以智能代理身份开设并操作钱包
KnightsPurse 是一款非托管的后量子钱包,为人们以及代表他们行事的自主智能代理和机器人而打造。本页是面向智能代理的操作指南。机器可读版本位于 /llms.txt(英文)。
撤销不会影响您在链上的资金。您的密钥归您所有,KnightsPurse 从不持有它们,因此这一层的任何操作都无法转移、冻结或限制您持有或支出的资产。链上支出限额随 Armature 上的代理账户提供,这是单独的一步。
您可以做什么
以无界面方式创建 / 加载钱包 派生后量子 KXCO ID 身份认证(双重签名) 关联到您的所有者 读取余额和历史记录 签名并广播交易 连接去中心化应用(EIP-6963) 处理商家记录(API 密钥) 接收已签名的 Webhook
量子安全的范围
后量子保护涵盖您的身份(ML-DSA-65)、身份认证以及密封同步(ML-KEM-768),另外还包括 Armature L1 上的原生交易。Bitcoin、Ethereum/EVM 和 Tron 交易按照各自协议的要求,使用各网络原生的椭圆曲线密码学签名。请勿作出与此不符的声明。
步骤
npm i kxco-post-quantumKXCO ID 为
fingerprint(ML-DSA-65 public key),由您的种子确定性地派生。
在您自己的环境中生成(或加载已预置的)BIP-39 种子。使用标准库派生各链密钥(例如 EVM 使用 viem),并使用 kxco-post-quantum 从同一种子派生您的 KXCO ID。密钥从不离开您的环境。
https://api.purse.kxco.ai,请求体为 JSON
使用您的 EVM 地址和 KXCO ID 请求一个质询。
POST /auth/challenge
{ "address": "0x…", "kxcoId": "<16 hex characters>" }
200 { "nonce": "<32 hex characters>", "message": "<the text to sign>" }
400 { "error": "invalid_address_or_kxco_id" }
消息由以下四行组成,以 \n 连接(消息文本为英文,须原样签名):
KnightsPurse wants you to sign in. Address: <address> KXCO ID: <kxcoId> Nonce: <nonce>
对这段文本原样签名两次,然后在 5 分钟内将两个签名发送至 /auth/verify。每个 nonce 只能使用一次。
| 字段 | 值 |
|---|---|
address | 与您发送至 /auth/challenge 的地址字符串相同。 |
kxcoId | 相同的 KXCO ID。 |
mlDsaPublicKey | 您的 ML-DSA-65 公钥,base64 编码。其指纹必须等于 kxcoId。 |
nonce | 质询返回的 nonce。 |
ecdsaSignature | 由 address 背后的密钥对消息生成的 EIP-191 personal_sign 签名,带 0x 前缀的十六进制。 |
mlDsaSignature | 由您的身份密钥对消息的 UTF-8 字节生成的 ML-DSA-65 签名,十六进制,不带上下文字符串。 |
POST /auth/verify
{ "address": "0x…", "kxcoId": "…", "mlDsaPublicKey": "…", "nonce": "…",
"ecdsaSignature": "0x…", "mlDsaSignature": "…" }
200 { "token": "…", "address": "0x…", "kxcoId": "…" }
401 { "error": "invalid or expired nonce" }
其他 401 原因包括 KXCO ID does not match the ML-DSA public key、ECDSA signature invalid 和 ML-DSA signature invalid。所有者已撤销的已关联代理会收到 403 { "error": "agent_revoked" }(第 05 步)。
以 Authorization: Bearer <token> 的形式发送令牌。令牌有效期为一小时,之后需重新登录。请将其视为不透明值:它是由平台签名的会话封装,而不是 JWT。
首次创建钱包时,登记一次:
POST /wallets/register { "kxcoId": "…" } -> { "ok": true, "isNew": true }
与网页钱包一致的密钥。任何 ML-DSA-65 密钥对都可以登录。若要在同一助记词下获得与网页钱包相同的 KXCO ID(从而访问相同的记录),请按以下方式派生:
- 种子:该助记词的 BIP-39 种子,不使用口令。
- EVM 密钥:路径
m/44'/60'/0'/0/0。Armature 和所有 EVM 链共用此路径。 - 身份密钥:使用
kxco-post-quantum中的mlDsa.keypairFromMaster(seed, "purse/identity/ml-dsa-65")。 - KXCO ID:
fingerprint(publicKey),即公钥 SHA-256 的前 16 个十六进制字符。
// npm i kxco-post-quantum viem @scure/bip39
import { mnemonicToSeedSync } from "@scure/bip39";
import { mnemonicToAccount } from "viem/accounts";
import { mlDsa, fingerprint } from "kxco-post-quantum";
const API = "https://api.purse.kxco.ai";
const post = async (path, body) => (await fetch(API + path, {
method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(body),
})).json();
const seed = mnemonicToSeedSync(phrase);
const id = mlDsa.keypairFromMaster(seed, "purse/identity/ml-dsa-65");
const kxcoId = fingerprint(id.publicKey);
const account = mnemonicToAccount(phrase); // m/44'/60'/0'/0/0
const { nonce, message } = await post("/auth/challenge", { address: account.address, kxcoId });
const { token } = await post("/auth/verify", {
address: account.address,
kxcoId,
mlDsaPublicKey: Buffer.from(id.publicKey).toString("base64"),
nonce,
ecdsaSignature: await account.signMessage({ message }),
mlDsaSignature: mlDsa.sign(id.secretKey, message),
});
// then: fetch(API + "/sync", { headers: { authorization: `Bearer ${token}` } })
您的所有者在网页钱包的已关联代理中创建一次性关联码,并交给您。您用 ML-DSA-65 身份密钥签名来兑换它。无需会话:签名即证明,服务器会检查您公钥的指纹是否就是您所声明的 KXCO ID,因此没有您的密钥,任何人都无法关联您的 KXCO ID。
POST /agents/link/challenge
{ "code": "link_…", "agentKxcoId": "<your KXCO ID>" }
200 { "message": "<the text to sign>", "ownerKxcoId": "…", "label": "…", "expiresAt": … }
400 { "error": "invalid_code" }
消息由以下四行组成,以 \n 连接。使用您的身份密钥对原文签名:UTF-8 字节、十六进制、不带上下文字符串。
KnightsPurse agent link. Owner KXCO ID: <ownerKxcoId> Agent KXCO ID: <agentKxcoId> Link code: <code>
POST /agents/link
{ "code": "link_…", "agentKxcoId": "…", "mlDsaPublicKey": "<base64>", "mlDsaSignature": "<hex>" }
200 { "ownerKxcoId": "…", "agentKxcoId": "…", "label": "…", "merchantScope": "write", "linkedAt": …, … }
401 { "error": "kxco_id_mismatch" }
401 { "error": "invalid_signature" }
409 { "error": "linked_to_another_owner" }
关联码为 link_ 加 40 个十六进制字符。它只能使用一次,并在所有者创建后 15 分钟失效;被拒绝的尝试不会消耗它。您只有一位所有者:来自其他任何人的关联码都会以 linked_to_another_owner 被拒绝。代理不能关联自己的代理:已关联的代理不能创建关联码(403 agents_cannot_own_agents),已拥有代理的钱包本身也不能被关联(409 agent_is_an_owner)。其他拒绝原因为 400 invalid_kxco_id、400 signature_required 和 400 cannot_link_self。
撤销,立即生效。使用您会话令牌的每个请求都会返回 403 agent_revoked,您的商家 API 密钥会被永久撤销(401 invalid_key),任何已打开的实时连接都会被关闭,新的登录也会返回 403 agent_revoked。只有同一位所有者可以恢复您,方法是提供一个新的关联码供您兑换;您原有的 API 密钥仍保持撤销状态。
商家工具。您的所有者可以将其设为只读。此后,对您商家记录(产品、收款链接、发票、收款、客户、Webhook 等)的修改,无论来自您的会话还是您的 API 密钥,都会返回 403 agent_read_only。您仍然可以创建和撤销自己的 API 密钥,但它们都无法写入。这不是支出限额。
不涉及您的资金。以上两项设置都不会作用于链上。链上支出限额以及所有者在链上的撤销权,随 Armature 上的代理账户提供,这是单独的一步。
GET /history?chainId=<id>&address=<addr> -> activity feed GET /fees/config -> platform fee (bps, collectors) # build + sign transactions locally, then broadcast via the chain RPC
请遵守 /fees/config 返回的平台费。
使用以 KnightsPurse 名义公布的注入式 EIP-1193 / EIP-6963 提供者,并根据您所有者的策略以编程方式批准或拒绝请求。
商家 API 密钥
商家 API 密钥让代理或集成服务处理其所有者的商家记录:产品、收款链接、发票、客户、收款等。它不需要助记词,也无法转移资金,因为后端不持有任何签名密钥。
- 所有者在网页钱包的商家工具 → API 密钥中创建:填写标签,如果该密钥可以修改记录则勾选允许写入,然后点击创建密钥。
- 或者,以所有者身份登录(第 03 步)后使用会话令牌调用:
POST /merchant/keys { "label": "Ops agent", "scopes": ["read", "write"] }。返回为{ "key": { "id", "label", "prefix", "scopes", "createdAt", … }, "token": "mk_…" }。scopes默认为["read"]。 - 令牌为
mk_加 48 个十六进制字符。它只显示一次,服务器只存储其 SHA-256 哈希。密钥不会过期。 - 所有者通过
GET /merchant/keys列出密钥(从不返回令牌),并通过DELETE /merchant/keys/:id撤销密钥。被撤销的密钥会立即被拒绝。
Authorization: Bearer mk_…
read:下表中所有的 GET 路由。write:另外还包括这些路由上的 POST、PUT 和 DELETE。没有该权限的密钥调用它们会收到 403。
| 记录 | 读取 | 写入(需要 write) |
|---|---|---|
| 产品 | GET /merchant/products | POST /merchant/products PUT /merchant/products/:id DELETE /merchant/products/:id |
| 收款链接 | GET /merchant/payment-links | POST /merchant/payment-links DELETE /merchant/payment-links/:id |
| 发票 | GET /merchant/invoices | POST /merchant/invoices PUT /merchant/invoices/:id DELETE /merchant/invoices/:id POST /merchant/invoices/:id/mark-paid |
| 折扣 | GET /merchant/discounts | POST /merchant/discounts DELETE /merchant/discounts/:id |
| 客户 | GET /merchant/customers | POST /merchant/customers PUT /merchant/customers/:id DELETE /merchant/customers/:id |
| 已收款项 | GET /merchant/payments | POST /merchant/payments/:id/refund |
| 定时付款 | GET /merchant/scheduled GET /merchant/scheduled/due | POST /merchant/scheduled DELETE /merchant/scheduled/:id |
| 订阅 | GET /merchant/subscriptions | POST /merchant/subscriptions DELETE /merchant/subscriptions/:id |
| Webhook | GET /merchant/webhooks | POST /merchant/webhooks DELETE /merchant/webhooks/:id |
| 通知设置 | GET /merchant/notifications GET /merchant/notifications/channels | PUT /merchant/notifications |
| 收款设置 | GET /merchant/settings | PUT /merchant/settings |
请求体即记录自身的字段。服务器会设置 id、merchantId 和 createdAt。金额为以该链原生单位表示的字符串,时间为自 1970 年起的毫秒数。
| 请求体 | 字段(? = 可选) |
|---|---|
| 产品 | name, description?, amount, chainId, active?(true) |
| 收款链接 | title, chainId, payToAddress, amount?(省略则由付款人决定金额), productId?, active?(true) |
| 发票 | number, items [{ description, amount, quantity, productId? }], total, chainId, payToAddress, customer?, customerId?, status?("draft"、"sent"、"paid" 或 "void";省略时为 "draft";付款被退款的发票为 "refunded"), dueDate? |
| 标记为已付款 | txHash?, amount?(省略时为发票总额), asset?, fromAddress? |
| 折扣 | code, type("percent" 或 "fixed"), value, active?(true) |
| 客户 | name, email?, note? |
| 退款 | txHash?:您从钱包发出的退款交易 |
| 定时付款 | to, amount, chainId, everyDays, nextRun, active?(true) |
| 订阅 | label, amount, chainId, payToAddress, intervalDays, customer?, customerId?, nextRun?(省略时为当前时间,因此第一期在下一次运行时开票), active?(true)。若缺少正数的 amount 和 intervalDays、chainId 或 payToAddress,请求会以 400 被拒绝。 |
| Webhook | url, events?(["payment.received"]) |
| 通知设置 | events, email?, telegramChatId? |
| 收款设置 | defaultChainId, payoutAddresses({ "<chainId>": "<address>" }), businessName?。每次保存都会整体替换 payoutAddresses,因此每次都要发送所有链。 |
只有在 Armature 上才会从链上自动匹配付款:状态为 "sent" 的发票,以及设有固定金额的收款链接。其他发票请通过 POST /merchant/invoices/:id/mark-paid 标记为已付款。
GET /merchant/notifications/channels 返回 { "email": true|false, "telegram": true|false }:即本服务器可用于发送通知的渠道。
付款人看到的页面:收款链接为 https://purse.kxco.ai/pay/<id>,状态为 "sent"、"paid" 或 "refunded" 的发票为 https://purse.kxco.ai/pay/invoice/<id>。KnightsPurse 不会向付款人发送任何电子邮件,因此"发送"发票即是分享该链接。
| 状态码 | 响应体 | 何时出现 |
|---|---|---|
| 401 | { "error": "invalid_key" } | mk_ 令牌不存在或已被撤销。 |
| 403 | { "error": "insufficient_scope" } | 没有 write 权限的密钥发送了 POST、PUT 或 DELETE。 |
| 404 | { "error": "not_found" } | 该 id 不属于此商家的记录。 |
| 401 | { "error": "unauthorized" } | 在需要所有者会话的路由上使用了密钥。 |
| 409 | { "error": "not_open" } | 对已付款或已退款的发票执行标记为已付款。 |
| 409 | { "error": "has_payment" } | 删除已付款或已退款的发票,或已记有付款的发票。 |
| 409 | { "error": "not_refundable" } | 对状态不是 "confirmed" 的付款执行退款。 |
| 400 | { "error": "url_required" } | Webhook 没有提供 url。 |
| 403 | { "error": "agent_read_only" } | 所有者已将其商家工具设为只读的已关联代理发送了 POST、PUT 或 DELETE,与密钥的权限范围无关。 |
| 403 | { "error": "agent_revoked" } | 该密钥所属的代理已被其所有者撤销。其密钥也会随之被撤销,并返回 invalid_key。 |
- 列出、创建或撤销密钥。
/merchant/keys只接受所有者的会话令牌。 - 查看 Webhook 签名密钥。使用密钥调用时,
GET和POST /merchant/webhooks不会返回secret。所有者可以在商家工具 → Webhook 中看到它。 - 调用需要已登录会话的路由,例如
/sync、/kyc/start、/kyc/status和/fees/quote。 - 转移资金或签署任何内容。
Webhook
Webhook 会在资金流动时通知商家自己的服务器。所有者在商家工具 → Webhook中添加一个以 https:// 开头的 URL 及其事件,或调用 POST /merchant/webhooks { "url": "https://…", "events": ["payment.received", "invoice.paid"] }。所有者收到的响应包含 "secret": "whsec_…",该密钥也会显示在商家工具中的这个 Webhook 上。使用密钥创建 Webhook 时不会返回 secret。
POST <your url>
content-type: application/json
x-purse-event: invoice.paid
x-purse-signature: sha256=<64 hex characters>
{ "event": "invoice.paid", "data": { … }, "ts": 1760000000000 }
ts 为发送时间(毫秒)。签名是对收到的原始请求体(原样)以完整的 secret 字符串(包括 whsec_)为密钥计算的 HMAC-SHA256,采用小写十六进制。请在解析 JSON 之前先校验签名。
import { createHmac, timingSafeEqual } from "node:crypto";
function fromKnightsPurse(rawBody, signatureHeader, secret) {
const expected = "sha256=" + createHmac("sha256", secret).update(rawBody).digest("hex");
const a = Buffer.from(signatureHeader ?? ""), b = Buffer.from(expected);
return a.length === b.length && timingSafeEqual(a, b);
}
| 事件 | data | 发送时机 |
|---|---|---|
payment.received | 付款 | 一笔 Armature 转账与已发送的发票或固定金额的收款链接相匹配,或某张发票被标记为已付款。 |
invoice.paid | 付款 | 一张发票结清(来自链上匹配或被标记为已付款)。它与 payment.received 一同发出。 |
payment.refunded | 付款,状态为 "refunded" | 一笔付款被标记为已退款。 |
invoice.created | 发票 | 创建了一张发票:由所有者、密钥,或订阅为其下一期创建。 |
- 付款(Payment):id, merchantId, invoiceId?, linkId?, chainId, fromAddress?, toAddress, amount, asset?, txHash?, status("pending"、"confirmed" 或 "refunded"), source("reconciled" 或 "manual"), createdAt, confirmedAt?, refundedAt?, refundTxHash?
- 发票(Invoice):id, merchantId, number, customer?, customerId?, items, total, chainId, payToAddress, status, dueDate?, createdAt
每个事件只发送一次,超时为 5 秒。不会重试,也不会跟随重定向。URL 必须是公共主机上的 https 地址,否则不会发送。随后 Webhook 的 lastStatus 会显示您的服务器返回的 HTTP 状态码、error 或 blocked_url。链上付款大约每 30 秒检查一次。
接口地址
| 服务 | URL |
|---|---|
| 网页钱包(界面) | https://app.purse.kxco.ai |
| API 基础地址 | https://api.purse.kxco.ai |
| 身份软件包(npm) | kxco-post-quantum |
| 浏览器扩展程序 | https://api.purse.kxco.ai/download/extension |
| 机器指南 | https://purse.kxco.ai/llms.txt |
行为良好的代理应遵守的规则
- 将密钥限制在您的环境内;切勿传输助记词或私钥。
- 在您所有者设定的限制范围内操作。每一项操作都可归属于所有者。
- 遵守平台费以及运营机构的条款。