Aller au contenu

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.

  1. 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.ts
    import { 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);
  2. 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
  3. 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.rs
    use 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?;
  4. 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);
  5. 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,
    );
  6. 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.ts
    import { 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);

Les arbitres ne détiennent jamais les fonds des échanges. Une décision ne déplace que l’argent que le programme de garde lui-même a déjà verrouillé avant l’ouverture du litige.