Aller au contenu principal

API

Chaque nœud OpenFiat expose un unique endpoint JSON-RPC 2.0 modelé directement sur la propre API JSON-RPC de Solana — des noms de méthode en camelCase getX/sendX sur un unique endpoint POST, plutôt qu’une hiérarchie de ressources REST. Il est implémenté par les crates rpc et api dans openfiat-core.

Endpoint

POST /rpc
Content-Type: application/json

Chaque requête est une enveloppe JSON-RPC 2.0 standard :

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

et chaque réponse est soit un result soit un error, jamais les deux :

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

Le nœud devnet public est à https://openfiat.allenhark.com — le même hôte publie aussi un multiaddr d’entrypoint pour les nœuds, qui est une adresse différente pour un travail différent (voir getting started). Tout nœud que vous exécutez vous-même sert la surface identique sur :7080.

Les clés, les peer ids et les signatures sont en base58

Chaque clé publique, identifiant de pair, signature et identifiant d’événement est une chaîne base58 :

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

C’étaient jusqu’à récemment des tableaux d’entiers. Si vous voyez "provider_public_key": [192, 74, 15, ...], vous parlez à un nœud antérieur au changement — et rien dans cette réponse ne distingue une clé publique publiée d’une clé privée fuitée, car un secret Ed25519 fait aussi trente-deux octets. Cette ambiguïté est la raison du changement. La forme base58 est aussi la seule utilisable : 12D3KooW… est ce qu’un --entrypoint prend et ce qu’on peut chercher dans un journal.

Ce n’est pas qu’un changement d’affichage. Une charge sendX est signée sur le JSON de son struct interne, donc un client qui écrit une clé dans une charge sous forme de tableau produit une transcription que le nœud ne reproduit pas. La signature échoue alors à la vérification — ce qui apparaît comme une mutation rejetée, pas comme une erreur d’analyse. Utilisez un SDK et c’est géré pour vous ; si vous construisez le format de fil à la main, encodez les identifiants en base58.

Les champs d’octets qui ne sont pas des identifiants restent des tableaux. Le commitment d’un vote de litige et son secret de révélation sont des valeurs opaques de trente-deux octets, pas des identités, et sont envoyés comme tableaux. La distinction tient à ce que le champ est, pas à sa longueur.

Nommage des méthodes

Les méthodes de lecture commencent par get et ne modifient jamais l’état :

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

Une méthode getMyX reste une lecture, mais elle ne répond que pour le portefeuille que l’appelant prouve détenir — voir lectures avec preuve de portefeuille.

Les mutations commencent par send et prennent un champ — data, une charge de fil encodée en base64 et déjà signée que le propre portefeuille de l’appelant a produite localement. Cela reflète le sendTransaction de Solana : le nœud ne construit ni ne signe jamais rien au nom de l’appelant, il décode seulement la charge et l’applique par le même chemin de vérification de signature que suit un événement reçu par gossip.

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

Le Client typé de chaque SDK construit et signe cette charge pour vous — c’est le point d’intégration recommandé, plutôt que de construire le format de fil à la main.

Catégories de méthodes

DomaineMéthodes en exemple
AnnoncesgetAdvertisement, getAdvertisements, sendAdvertisementCreate, sendAdvertisementPriceUpdate, sendAdvertisementDisable
RéservationsgetReservation, getReservations, getMyReservations, sendReservationRequest
RèglementgetSettlement, getSettlements, getMySettlements, sendSettlementInitiate, sendPaymentSubmitted, sendSettlementApproved
Échange (jointure en lecture seule)getTrade, getTrades
LitigesgetDispute, getDisputes, getMyDisputes, sendDisputeOpen, sendArbitratorJoin, sendVoteCommit, sendVoteReveal
Preuves de portefeuillegetWalletChallenge, getCounterpartiesChallenge, getCounterparties, getProviderEarningsChallenge
VolumegetSettledVolume
Pièces jointes et contenugetSettlementAttachments, getHeldContent, sendAttachmentPublish
IdentitégetIdentityClaim, getIdentityClaimsByWallet, sendClaimPublish
Réputation (lecture seule)getReputation
GouvernancegetProposal, getProposals, sendProposalCreate, sendVoteCast
Fournisseurs de servicegetProvider, getProviders, sendProviderRegister, sendProviderHealthUpdate, getProviderEarnings, sendProviderWithdraw
NotificationsgetSubscription, getNotificationDispatch, sendSubscriptionUpdate, sendDeliveryReport
OraclesgetOracleRecord, getExchangeRate, getMedianExchangeRate, sendOraclePublish
Intelligence des risquesgetWalletScreening, sendRiskPublish
RécompensesgetRewardObservations
SnapshotsgetLatestSnapshot, getSnapshots, getCheckpointSlot, sendSnapshotAnnounce
SessionsgetSession, sendSessionEstablish, sendSessionRenew, sendSessionRevoke, sendSessionMigrate
Pont de chaîne (Solana, OFS-4300)getChainStatus, getLatestBlockhash, sendTransaction
NœudgetVersion, getHealth, getPeers

Lectures qui ne nomment pas les parties

getSettlement(s), getReservation(s) et getDispute(s) renvoient des enregistrements dont l’identité des parties est retirée. C’est un changement délibéré, motivé par la sécurité, et non un oubli, et il a une page à lui : ce que renvoie une lecture publique. Une partie lit ses propres enregistrements en entier via les lectures avec preuve de portefeuille.

Les litiges sont décidés sur la chaîne, pas par le nœud qui vous répond

Une réponse de getDispute porte les révélations qu’un nœud a collectées, et ne porte pas d’issue dérivée d’elles. Une résolution n’apparaît qu’une fois que ce nœud a observé la transaction d’exécution se confirmer et lu ce qu’elle a décidé — voir comment un litige se résout. Un client qui décompte lui-même les révélations a réintroduit exactement la divergence que le nœud a cessé de produire.

Deux méthodes de taux de change, et laquelle choisir

getMedianExchangeRate renvoie un nombre nu ou null, ce qui est la bonne forme quand tout ce que vous voulez est un prix ou rien.

getExchangeRate prend les mêmes { base, quote } et répond plutôt par un statut étiqueté, car null confond deux faits distincts :

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

La distinction n’est pas académique. Stale signifie qu’un fournisseur publie bien cette paire et que chaque enregistrement a expiré (OFS-7000 §12 : des données expirées ne sont pas des données courantes, si récent que soit le lapse) — le flux reviendra probablement, donc attendre est sensé. NoData signifie que personne ne price ce corridor et qu’attendre est inutile. Ni l’un ni l’autre n’est un nombre, et un appelant ne doit afficher ni l’un ni l’autre comme tel.

Choisissez getExchangeRate sauf raison contraire. getMedianExchangeRate demeure parce que les clients en dépendent.

Service ids

Un nœud s’enregistre sous node-<sa clé publique base58>, et un fournisseur de snapshots sous snapshot-<la même clé> — le préfixe est ce qui permet à un nœud de tenir plusieurs enregistrements du registre sans qu’ils entrent en collision.

L’id est dérivé plutôt qu’aléatoire pour qu’un nœud qui redémarre mette à jour son enregistrement existant au lieu de laisser une entrée morte derrière lui. Le dériver de la clé entière importe : un schéma antérieur utilisait les huit premiers octets du peer id en hexadécimal, ce qui ressemble à seize chiffres d’identité mais en fait deux, car chaque peer id Ed25519 s’ouvre sur le même préambule de six octets. Deux nœuds sont entrés en collision en quelques centaines d’enregistrements, et le second à s’enregistrer a délogé le premier.

Ce qu’un nœud sait du réseau

getPeers rapporte les pairs que ce nœud a découverts, les adresses qu’il annonce sur lui-même, et son propre self_peer_id sous la forme 12D3Koo… qui va dans un --entrypoint. Voir découverte de pairs pour la vue de l’opérateur là-dessus.

Erreurs

Les codes d’erreur JSON-RPC 2.0 standard (-32700 erreur d’analyse, -32601 méthode introuvable, -32602 paramètres invalides, -32603 erreur interne) couvrent les échecs de niveau transport. Chaque échec de domaine — liquidité insuffisante, un événement doublon, un signataire non autorisé — revient comme une unique erreur d’application -32000, avec le code numérique propre du protocole et le nom symbolique (de OFS-8000) dans data :

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

Abonnements

GET /ws

diffuse chaque mutation réussie au moment où elle se produit — {"method": "sendX", "result": ...} — pour qu’un client puisse réagir à l’activité du marché sans interrogation. Filtrez côté client selon les méthodes qui vous intéressent.

Les snapshots sont étiquetés par slot, pas par une hauteur

getCheckpointSlot renvoie le slot Solana par rapport auquel l’état du dernier snapshot importé était courant, ou null sur un nœud qui n’en a importé aucun.

C’était getCheckpointHeight, et le renommage n’est pas cosmétique. L’ancienne valeur était le propre décompte d’événements de gossip du nœud producteur, qui est par producteur : deux nœuds au même état rapportent des nombres différents, et un nœud entré la semaine dernière en rapporte un plus bas qu’un nœud en marche depuis la genèse. Comparer les nombres de deux producteurs ne comparait rien.

Un slot est la seule horloge que chaque participant partage déjà. Il rend aussi une affirmation vérifiable — un nœud peut comparer un slot annoncé à sa propre vue de la chaîne et en refuser un d’un futur invraisemblable, ce qui est impossible contre un nombre que seul l’annonceur peut voir.

Ce qu’un slot affirme est plus étroit qu’il n’y paraît. Il dit quand l’état a été capturé, pas ce qu’il contient : deux nœuds prenant un snapshot au même slot peuvent tenir un état de gossip légèrement différent, car la propagation n’est pas instantanée. Traitez-le comme une ancre de récence, pas comme une preuve qu’un snapshot en contient un autre — la même chose que signifient à son sujet les propres snapshots de Solana.

Un nœud qui n’a jamais observé de slot ne produit pas de snapshots et le dit. Ce n’est pas une exigence d’exécuter une connexion RPC : un nœud gossip-seul apprend les slots par le pont de chaîne.

Volume réglé, et pourquoi il est par actif

getSettledVolume répond par une ligne par actif, jamais un total :

{
"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"
}

Quatre choses qu’un client ne doit pas faire avec ceci :

Ne sommez pas entre actifs. Ce sont des tokens différents à des échelles différentes ; un chiffre combiné ajoute du SOL à de l’USDC et ne signifie rien.

Ne devinez pas decimals. Il vaut null, aux côtés d’un asset_symbol null, quand ce nœud n’a pas de nom pour ce mint. Affichez l’adresse et les unités de base brutes. Supposer 6 est exactement comment wSOL — qui en a neuf — ressort mille fois trop grand.

Ne cachez pas unattributed_settlements. Ce sont de vraies liquidations confirmées dont l’annonce a depuis été supprimée, donc leur actif est irrécupérable. Les omettre fait paraître les totaux complets alors qu’il en manque d’autant.

Ne supprimez pas scope. Il dit que ce sont les liquidations que ce nœud a répliquées et confirmées — pas toute l’histoire du réseau. Un chiffre de volume présenté sans son périmètre se lit comme un total global. settlements_known à côté des lignes comptées fait lire le reste comme des échanges en cours, et non comme un écart.

Référence interactive

Parcourez chaque méthode →

Un document OpenRPC 1.2.6 (l’équivalent JSON-RPC d’une spec OpenAPI/Swagger) — /api/openrpc.json — plus une page interactive autonome pour parcourir chaque méthode. La liste des méthodes est générée directement depuis la propre table de dispatch en direct de openfiat-rpc (cargo run -p openfiat-api --example dump_openrpc), elle ne peut donc dériver vers une méthode qu’un vrai nœud n’exécute pas ; elle est publiée ici comme un snapshot statique puisque ce site de docs n’a pas de nœud à lui pour la servir en direct. Pointez le panneau « Try it » de la page de référence vers un nœud que vous exécutez vous-même (par défaut http://localhost:7080) pour appeler une méthode pour de vrai.

Les schémas par méthode dans ce document sont une approximation délibérément simplifiée et fondée sur des conventions — chaque getX(id) prend {id}, chaque sendX prend {data} — plutôt qu’un JSON Schema dérivé des types Rust concrets de chaque méthode. Là où une méthode s’écarte de ces conventions, ce site est la forme faisant autorité : les lectures avec preuve de portefeuille, getExchangeRate et getPeers prennent toutes des paramètres que la convention ne décrit pas.

Un nœud en marche sert aussi la référence identique en direct et de même origine que son propre /rpc : GET /openrpc.json et GET /docs. GET /metrics expose des compteurs de requêtes au format Prometheus pour les opérateurs.