跳到主要内容

API

每个 OpenFiat 节点都暴露一个直接以 Solana 自身 JSON-RPC API 为蓝本的 JSON-RPC 2.0 端点——在单个 POST 端点上使用 getX/sendX 这样的 camelCase 方法名,而非 REST 资源层级。它由 openfiat-core 中的 rpcapi crate 实现。

端点

POST /rpc
Content-Type: application/json

每个请求都是一个标准的 JSON-RPC 2.0 信封:

{ "jsonrpc": "2.0", "id": 1, "method": "getAdvertisement", "params": { "id": "ad-1" } }

而每个响应要么是 result,要么是 error,绝不同时出现:

{ "jsonrpc": "2.0", "id": 1, "result": { "id": "ad-1", "status": "Active", "..." : "..." } }

公共 devnet 节点位于 https://openfiat.allenhark.com——同一主机 还为节点发布一个入口点 multiaddr,那是用于不同工作的不同 地址(参见 getting started)。 你自己运行的任何节点都在 :7080 上提供相同的接口。

密钥、peer id 和签名都是 base58

每个公钥、peer 标识符、签名和事件标识符都是一个 base58 字符串:

{
"service_id": "node-ALLENLMtV1zEAHT3xpVryqcbdPCB8c9JhM1Jdbe5XHg5",
"provider": "12D3KooWK9hQ7TwbfvFiaAxUbRFCkdhS7iEpAJDnewNL1anyREQ1",
"provider_public_key": "ALLENLMtV1zEAHT3xpVryqcbdPCB8c9JhM1Jdbe5XHg5"
}

直到不久前它们还是整数数组。如果你看到 "provider_public_key": [192, 74, 15, ...],说明你正在与一个 早于此变更的节点通信——而该响应中没有任何东西能把已发布的 公钥与泄露的私钥区分开,因为一个 Ed25519 私钥 同样是三十二字节。正是这种含混才促成了变更。base58 形式也是唯一可用的:12D3KooW… 才是 --entrypoint 所接受、 也是能在日志中被搜索到的东西。

这不仅是显示上的变更。 一个 sendX 载荷是针对其内部结构体的 JSON 进行签名的,因此一个把密钥以数组形式写入载荷的客户端, 会产生一份节点无法复现的记录。签名于是 验证失败——这表现为一次被拒绝的变更,而 不是解析错误。使用SDK就会替你处理好这些;若要 手写线格式,请将标识符编码为 base58。

不是标识符的字节字段仍保持为数组。一个争议投票的 commitment 及其揭示的 secret 是不透明的三十二字节值, 而非身份,会作为数组发送。区分依据是字段是什么, 而非其长度。

方法命名

读取方法以 get 开头,绝不改变状态:

{ "method": "getReservation", "params": { "id": "res-1" } }
{ "method": "getReservations", "params": {} }

getMyX 方法仍然是一次读取,但它只对调用方证明其持有的 钱包作答——参见钱包证明读取

变更方法以 send 开头,并取一个字段——data,一份 base64 编码、 已签名的线格式载荷,由调用方自己的钱包在本地产生。 这与 Solana 的 sendTransaction 相仿:节点从不代表调用方 构造或签署任何东西,它只解码载荷,并通过一个 gossip 收到的 事件所经过的同一签名验证路径来应用它。

{ "method": "sendReservationRequest", "params": { "data": "<base64 wire bytes>" } }

每个SDK的类型化 Client 都会替你构建并签署该载荷—— 它是推荐的集成点,而非手工构造线格式。

方法类别

领域示例方法
广告getAdvertisement, getAdvertisements, sendAdvertisementCreate, sendAdvertisementPriceUpdate, sendAdvertisementDisable
预约getReservation, getReservations, getMyReservations, sendReservationRequest
结算getSettlement, getSettlements, getMySettlements, sendSettlementInitiate, sendPaymentSubmitted, sendSettlementApproved
交易(只读联接)getTrade, getTrades
争议getDispute, getDisputes, getMyDisputes, sendDisputeOpen, sendArbitratorJoin, sendVoteCommit, sendVoteReveal
钱包证明getWalletChallenge, getCounterpartiesChallenge, getCounterparties, getProviderEarningsChallenge
交易量getSettledVolume
附件与内容getSettlementAttachments, getHeldContent, sendAttachmentPublish
身份getIdentityClaim, getIdentityClaimsByWallet, sendClaimPublish
声誉(只读)getReputation
治理getProposal, getProposals, sendProposalCreate, sendVoteCast
服务提供者getProvider, getProviders, sendProviderRegister, sendProviderHealthUpdate, getProviderEarnings, sendProviderWithdraw
通知getSubscription, getNotificationDispatch, sendSubscriptionUpdate, sendDeliveryReport
预言机getOracleRecord, getExchangeRate, getMedianExchangeRate, sendOraclePublish
风险情报getWalletScreening, sendRiskPublish
奖励getRewardObservations
快照getLatestSnapshot, getSnapshots, getCheckpointSlot, sendSnapshotAnnounce
会话getSession, sendSessionEstablish, sendSessionRenew, sendSessionRevoke, sendSessionMigrate
链桥(Solana,OFS-4300)getChainStatus, getLatestBlockhash, sendTransaction
节点getVersion, getHealth, getPeers

不指明当事方的读取

getSettlement(s)getReservation(s)getDispute(s) 返回的记录 已移除当事方身份。这是一个出于安全考量的刻意变更, 而非疏漏,它有自己独立的一页: 公开读取返回什么。当事方通过 钱包证明读取完整读取自己的记录。

争议在链上裁决,而非由答复你的节点裁决

一个 getDispute 响应携带某节点已收集的揭示,且 携带由它们推导出的结果。裁决只在此节点已观察到执行交易 确认并读取其裁决内容之后才出现——参见 争议如何解决。一个自行清点 揭示的客户端,已经重新引入了节点已停止产生的那种 分歧。

两个汇率方法,以及该用哪一个

getMedianExchangeRate 返回一个裸数字或 null,当你所要的 只是一个价格或什么都没有时,这是恰当的形态。

getExchangeRate 取相同的 { base, quote },但以带标签的 状态作答,因为 null 会把两个不同的事实混为一谈:

{ "status": "current", "rate": 129.5, "expiresAt": 1753800000000 }
{ "status": "stale" }
{ "status": "noData" }

这个区分并非学究之见。Stale 意味着确有提供者发布此 交易对,且每条记录都已过期(OFS-7000 §12:过期数据不是当前 数据,无论其失效有多近)——数据源很可能会恢复,因此等待 是明智的。NoData 意味着根本没人为这条通道定价,等待 毫无意义。二者都不是数字,调用方也不得将任何一个显示为数字。

除非你有理由不这么做,否则请使用 getExchangeRategetMedianExchangeRate 之所以保留,是因为客户端依赖它。

Service id

一个节点以 node-<其 base58 公钥> 注册,而一个快照 提供者以 snapshot-<同一密钥> 注册——前缀正是让一个 节点持有多条注册记录而不致相撞的原因。

该 id 是推导出来的而非随机的,从而使一个重启的节点更新 其现有记录,而不是留下一条死条目。从整个密钥 推导它很重要:早先的一种方案把 peer id 的前八个 字节当作十六进制,看起来像十六位身份数字, 其实只有两位,因为每个 Ed25519 peer id 都以相同的六字节前缀开头。 两个节点在数百次注册之内就相撞了,而 后注册者取代了先注册者。

一个节点对网络知道什么

getPeers 报告此节点已发现的 peer、它关于自身所 公布的地址,以及它自己那个 12D3Koo… 形式、用于 --entrypointself_peer_id。参见 peer 发现了解操作者视角。

错误

标准的 JSON-RPC 2.0 错误码(-32700 解析错误、-32601 方法未 找到、-32602 参数无效、-32603 内部错误)覆盖了传输 层面的失败。每一个领域层面的失败——流动性不足、重复 事件、未授权的签名方——都作为单一的 -32000 应用 错误返回,并在 data 中附带协议自身的数字码和符号名(来自 OFS-8000):

{ "error": { "code": -32000, "message": "INSUFFICIENT_AVAILABLE_LIQUIDITY", "data": { "ofsErrorCode": 4004, "ofsErrorName": "INSUFFICIENT_AVAILABLE_LIQUIDITY" } } }

订阅

GET /ws

在每一次成功变更发生时将其流式推送——{"method": "sendX", "result": ...}——从而使客户端无需轮询即可对市场活动作出反应。在客户端按你关心的方法进行过滤。

快照按 slot 标记,而非按高度

getCheckpointSlot 返回上一次导入的快照状态所对应为当前的 Solana slot,或在尚未导入任何快照的节点上返回 null

它曾是 getCheckpointHeight,而这次改名并非表面文章。旧值 是生产该快照的节点自己对 gossip 事件的计数,这是 逐生产者而异的:两个持有相同状态的节点报告不同的数字, 一个上周才加入的节点报告的数字,低于一个自创世起就 运行的节点。比较两个生产者的数字什么也没比较。

一个 slot 是每个参与者早已共享的那一个时钟。它还使一个 断言变得可核验——一个节点可以把公布的 slot 与它自己 对链的视图相比较,并拒绝一个来自不合情理之未来的 slot,而 这对一个只有公布者才能看到的数字是不可能的。

一个 slot 所断言的东西比看起来要狭窄。 它说明状态是何时 被捕获的,而非它包含什么:两个在同一 slot 做快照的 节点,可能持有略有不同的 gossip 状态,因为传播并非 即时。把它当作一个近因锚点,而非一个快照 包含另一个的证明——这与 Solana 自身快照对它的含义相同。

一个从未观察到 slot 的节点不产生快照,并会如此声明。 这并不要求运行一个 RPC 连接:一个仅 gossip 的节点 通过链桥学习 slot。

已结算交易量,以及为何按资产统计

getSettledVolume 以每个资产一行作答,绝不给出总计:

{
"assets": [
{ "asset_mint": "2bHPi…RRU", "asset_symbol": "USDC", "decimals": 6,
"base_units": 4500000, "settlements": 12 },
{ "asset_mint": "So111…112", "asset_symbol": "wSOL", "decimals": 9,
"base_units": 2000000000, "settlements": 3 }
],
"unattributed_settlements": 1,
"settlements_known": 16,
"scope": "settlements this node has replicated and observed confirmed"
}

对此,客户端有四件事绝不能做:

不要跨资产求和。 它们是不同尺度的不同代币; 一个合并的数字把 SOL 加到 USDC 上,毫无意义。

不要猜测 decimals 当此节点对某个 mint 没有名称时, 它连同一个 nullasset_symbol 一起为 null。请显示地址 和原始的 base units。假定为 6 正是 wSOL——它有九位—— 会算大一千倍的原因。

不要隐藏 unattributed_settlements 那些是真实的、已确认的 结算,其广告此后已被删除,因此其资产 无法恢复。省略它们会让总计看起来完整,而实际上 恰恰少了那么多。

不要丢弃 scope 它说明这些是此节点 复制并确认的结算——而非整个网络的历史。一个不带其 范围呈现的交易量数字,会被读作全局总计。settlements_known 与已计数的行并列,会让余数被读作在途交易,而 非一处不一致。

交互式参考

浏览每个方法 →

一份 OpenRPC 1.2.6 文档(JSON-RPC 相当于 OpenAPI/Swagger 规范的等价物)——/api/openrpc.json—— 外加一个自包含的交互式页面,用于浏览每个方法。方法列表 直接由 openfiat-rpc 自身的活动分派表生成 (cargo run -p openfiat-api --example dump_openrpc),因此它不可能漂移到 一个真实节点并不运行的方法上;由于本文档站点没有自己的 节点来实时提供它,它在此以静态快照发布。把参考页面的 「Try it」面板指向你自己运行的一个节点(默认为 http://localhost:7080),即可真正调用一个方法。

该文档中的逐方法模式是一种刻意简化、基于约定的 近似——每个 getX(id){id},每个 sendX{data}——而非从每个方法具体的 Rust 类型派生出的 JSON Schema。当某个方法偏离这些约定时,本站点才是 权威形态:钱包证明读取getExchangeRategetPeers 都取约定未 描述的参数。

一个运行中的节点也会以与其自身 /rpc 同源、实时的方式 提供相同的参考:GET /openrpc.jsonGET /docsGET /metrics 为操作者暴露 Prometheus 格式的请求计数器。