Pular para o conteúdo

Torne-se um árbitro OpenFiat

Os árbitros decidem disputas com o próprio OPEN em risco, não a reputação. Isto percorre o bond, encontrar um caso e emitir o voto de comprometer-então-revelar que de fato paga.

O que você precisa

  • Uma carteira Solana com pelo menos 10.000 OPEN — o min_stake_arbitrator da configuração de staking implantada, que a governança pode mudar — faça bond dela pela página Stake do openfiat-app, ou pelas instruções abaixo se você estiver construindo seu próprio cliente
  • Uma carteira que consiga assinar mensagens, o que toda carteira Solana relevante faz — a página Arbitrate do openfiat-app roda o caso inteiro no navegador, e os SDKs estão aí se você preferir programar
  • Acesso de rede ao endpoint JSON-RPC de pelo menos um nó OpenFiat, o seu ou um público

Trabalhando um caso

Trabalhe nestes passos em ordem, um caso de cada vez. Cada passo traz o comando ou código de que precisa.

  1. 01

    Faça bond de OPEN para desbloquear o pool de arbitragem

    Os árbitros precisam fazer stake antes de poderem ver as provas de um único caso — é o que torna subornar um deles inútil (você não sabe qual caso mirar) e dá à rede algo para dar slash se você votar contra o consenso revelado. A configuração de devnet implantada define o mínimo de árbitro em 10.000 OPEN, dez vezes os 1.000 que todo outro papel aporta.

    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

    Encontre um caso aberto

    Os árbitros escolhem quais disputas trabalhar — ninguém lhe designa uma. Consulte qualquer nó por casos que ainda não atingiram a contagem exigida de árbitros.

    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

    Ingresse antes de poder ver as provas

    Ingressar é o que desbloqueia um caso para você: os próprios envios do comprador e do vendedor, as confirmações de pagamento e o log de mensagens da negociação deles. Uma vez que um caso tem seu complemento completo de árbitros, ele trava e a fase de compromisso começa.

    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

    Comprometa seu voto — duas vezes

    Dois votos de comprometer-revelar rodam lado a lado: um off-chain que registra na própria trilha de auditoria e reputação do caso, e um on-chain contra a conta DisputeCase do openfiat-escrow que de fato decide o resultado ponderado por stake. Use a mesma decisão e o mesmo salt para ambos — mas o enum próprio de cada lado, não o mesmo número: Invalid off-chain é 2, InvalidDispute on-chain é 3, porque MutualSettlement fica em 2 on-chain. Faça o hash do byte errado e você se compromete a algo que nunca poderá revelar, que é exatamente o que faz um árbitro levar slash.

    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

    Revele assim que a janela abrir

    Revele seu resultado e salt em ambos os lugares assim que a janela de compromisso fechar. On-chain, é também aqui que seu voto ganha seu peso real: a instrução de reveal lê sua conta de stake do papel Arbitrator diretamente, então uma carteira sem stake de árbitro simplesmente não consegue fornecer um válido.

    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

    O resultado se executa sozinho

    Uma vez que todo árbitro revelou, ou a janela de reveal fecha, qualquer um — você, o comprador, o vendedor ou um bot não relacionado — pode chamar execute_dispute_outcome. Ele só conta os votos já registrados on-chain. O design então paga à maioria uma parcela das taxas do caso e retira parte do stake de quem revelou contra ela — mas nem a recompensa nem a penalidade estão implementadas, então hoje a contagem move os fundos dos traders e nada mais.

    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);

Os árbitros nunca guardam fundos de negociações. Uma decisão só move dinheiro que o próprio programa de custódia já bloqueou antes de a disputa ser aberta.