Devenir arbitre OpenFiat
Les arbitres tranchent les litiges avec leur propre OPEN en jeu, pas leur réputation. Ceci parcourt le bond, la recherche d’un cas et l’émission du vote engagement-puis-révélation qui paie effectivement.
Ce qu’il vous faut
- Un portefeuille Solana avec au moins 10 000 OPEN — le min_stake_arbitrator de la configuration de staking déployée, que la gouvernance peut modifier — mettez-le en bond via la page Stake d’openfiat-app, ou via les instructions ci-dessous si vous construisez votre propre client
- Un portefeuille capable de signer des messages, ce que fait tout portefeuille Solana pertinent — la page Arbitrate d’openfiat-app exécute tout le dossier dans le navigateur, et les SDK sont là si vous préférez programmer
- Un accès réseau à l’endpoint JSON-RPC d’au moins un nœud OpenFiat, le vôtre ou un public
Traiter un cas
Suivez ces étapes dans l’ordre, un cas à la fois. Chaque étape apporte la commande ou le code dont elle a besoin.
- 01
Mettez OPEN en bond pour débloquer le pool d’arbitrage
Les arbitres doivent mettre en stake avant de pouvoir voir les preuves d’un seul dossier — c’est ce qui rend inutile d’en corrompre un (vous ne savez pas quel dossier viser) et donne au réseau quelque chose à slasher si vous votez contre le consensus révélé. La configuration de devnet déployée fixe le minimum d’arbitre à 10 000 OPEN, dix fois les 1 000 que chaque autre rôle apporte.
stake.tsimport { PublicKey, Transaction } from "@solana/web3.js"; import { getAssociatedTokenAddressSync, TOKEN_2022_PROGRAM_ID } from "@solana/spl-token"; import { onchain } from "@openfiat/sdk"; // Role.Arbitrator = 1 — same instruction pair openfiat-app's Stake page // submits when you connect a wallet and bond there directly. const from = getAssociatedTokenAddressSync(mint, owner, false, TOKEN_2022_PROGRAM_ID); const amount = 10_000n * 1_000_000_000n; // OPEN has 9 decimals (OFS-4100 §1) const instructions = [ onchain.staking.initializeStakeAccountIx(owner, onchain.Role.Arbitrator), onchain.staking.stakeIx(owner, mint, onchain.Role.Arbitrator, from, amount), ]; const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash(); const tx = new Transaction({ feePayer: owner, blockhash, lastValidBlockHeight }).add(...instructions); const { signature } = await wallet.signAndSendTransaction(tx); - 02
Trouvez un dossier ouvert
Les arbitres choisissent les litiges sur lesquels travailler — personne ne vous en affecte un. Interrogez n’importe quel nœud pour les dossiers qui n’ont pas encore atteint le nombre d’arbitres requis.
shell# Any node will do — arbitrators pick a case, nobody assigns one. curl -s -X POST http://localhost:7080/rpc -H 'content-type: application/json' \ -d '{"jsonrpc":"2.0","id":1,"method":"getDisputes","params":{}}' # Filter the result for status: "Open" and arbitrators.length < required_arbitrators - 03
Adhérez avant de pouvoir voir les preuves
Adhérer est ce qui débloque un dossier pour vous : les soumissions de l’acheteur et du vendeur, les confirmations de paiement et le journal de messages de leur échange. Une fois qu’un dossier a son complément complet d’arbitres, il se verrouille et la phase d’engagement commence.
join.rsuse openfiat_disputes::events::ArbitratorJoin; use openfiat_sdk::wallet::Keypair; use openfiat_sdk::{Client, ClientConfig}; // Joining is what unlocks the case's evidence for you — buyer/seller // submissions and the trade's own message log become visible once you're // in `dispute.arbitrators`. let client = Client::new(ClientConfig { endpoint: node_url, ..Default::default() }); client .send_arbitrator_join( ArbitratorJoin { dispute_id: dispute.id.clone(), arbitrator: peer_id(&keypair), arbitrator_public_key: keypair.public_key(), timestamp: Timestamp::now(), }, &keypair, ) .await?; - 04
Engagez votre vote — deux fois
Deux votes engagement-révélation tournent côte à côte : un off-chain qui s’inscrit dans la piste d’audit et de réputation propre au dossier, et un on-chain contre le compte DisputeCase d’openfiat-escrow qui décide réellement de l’issue pondérée par stake. Utilisez la même décision et le même salt pour les deux — mais l’enum propre à chaque côté, pas le même nombre : Invalid off-chain vaut 2, InvalidDispute on-chain vaut 3, parce que MutualSettlement occupe 2 on-chain. Hachez le mauvais octet et vous vous engagez sur quelque chose que vous ne pourrez jamais révéler, ce qui est exactement ce qui fait slasher un arbitre.
commit.rs + commit.ts// Off-chain (marketplace record + reputation) — Rust SDK let secret: [u8; 32] = rand::random(); let commitment = openfiat_disputes::commitment::compute(vote, &secret); // sha256(vote || secret) client.send_vote_commit(VoteCommit { dispute_id, arbitrator: peer_id(&keypair), commitment, timestamp: Timestamp::now() }, &keypair).await?; // keep `secret` — you need it again at reveal, and it must never be guessable before then // On-chain (the stake-weighted vote that actually pays out) — TypeScript SDK import { onchain } from "@openfiat/sdk"; import { createHash } from "node:crypto"; // onchain.DisputeOutcome, NOT the off-chain Vote byte: the two enums // diverge from Invalid onwards (see this file's header). Hash the wrong // one and the commitment can never be opened. const commitment = createHash("sha256").update(Buffer.from([onchain.DisputeOutcome.BuyerWins])).update(salt).digest(); const ix = onchain.escrow.commitDisputeVoteIx(arbitrator, reservationId, commitment); - 05
Révélez dès que la fenêtre s’ouvre
Révélez votre issue et votre salt aux deux endroits dès que la fenêtre d’engagement se ferme. On-chain, c’est aussi ici que votre vote gagne son poids réel : l’instruction de reveal lit directement votre compte de stake du rôle Arbitrator, si bien qu’un portefeuille sans stake d’arbitre ne peut tout simplement pas en fournir un valide.
reveal.rs + reveal.ts// Off-chain — Rust SDK client.send_vote_reveal(VoteReveal { dispute_id, arbitrator: peer_id(&keypair), vote, secret, timestamp: Timestamp::now() }, &keypair).await?; // On-chain — reads your Arbitrator StakeAccount directly (no CPI) to weight this vote import { onchain } from "@openfiat/sdk"; const [arbitratorStake] = onchain.staking.stakeAccountPda(arbitrator, onchain.Role.Arbitrator); const ix = onchain.escrow.revealDisputeVoteIx( arbitrator, reservationId, onchain.DisputeOutcome.BuyerWins, salt, arbitratorStake, ); - 06
L’issue s’exécute d’elle-même
Une fois que chaque arbitre a révélé, ou que la fenêtre de reveal se ferme, n’importe qui — vous, l’acheteur, le vendeur ou un bot sans rapport — peut appeler execute_dispute_outcome. Il ne compte que les votes déjà enregistrés on-chain. Le design paie alors à la majorité une part des frais du dossier et retire une partie du stake de qui a révélé contre elle — mais ni la récompense ni la pénalité ne sont implémentées, donc aujourd’hui le décompte déplace les fonds des traders et rien de plus.
resolve.tsimport { onchain } from "@openfiat/sdk"; // Permissionless — it only tallies votes the reveal step above already // recorded on-chain, so anyone (the buyer, the seller, either arbitrator, // or an unrelated crank bot) can call it once the reveal window closes. const ix = onchain.escrow.executeDisputeOutcomeIx(mint, seller, reservationId, destinations);