Conviértete en árbitro de OpenFiat
Los árbitros deciden disputas con su propio OPEN en riesgo, no su reputación. Esto recorre el depósito, encontrar un caso y emitir el voto de comprometer-luego-revelar que efectivamente paga.
Qué necesitas
- Una billetera de Solana con al menos 10.000 OPEN —el min_stake_arbitrator de la configuración de staking desplegada, que la gobernanza puede cambiar—; deposítalos mediante la página Stake de openfiat-app, o las instrucciones de abajo si construyes tu propio cliente
- Una billetera que pueda firmar mensajes, algo que hace toda billetera de Solana importante: la página Arbitrate de openfiat-app ejecuta todo el caso en el navegador, y los SDK están ahí si prefieres programarlo
- Acceso de red al endpoint JSON-RPC de al menos un nodo OpenFiat, el tuyo o uno público
Trabajar un caso
Sigue estos pasos en orden, un caso a la vez. Cada paso incluye el comando o código que necesita.
- 01
Deposita OPEN para desbloquear el fondo de arbitraje
Los árbitros deben hacer stake antes de poder ver las pruebas de un solo caso: eso es lo que hace inútil sobornar a uno (no sabes a qué caso apuntar) y le da a la red algo que recortar si votas contra el consenso revelado. La configuración desplegada de devnet fija el mínimo de árbitro en 10.000 OPEN, diez veces los 1.000 que aporta cada otro rol.
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
Encuentra un caso abierto
Los árbitros eligen qué disputas trabajar; nadie te asigna una. Consulta cualquier nodo para ver los casos a los que aún les falta su número requerido 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 - 03
Únete antes de poder ver las pruebas
Unirse es lo que te desbloquea un caso: las presentaciones del comprador y del vendedor, las confirmaciones de pago y el registro de mensajes de su operación. Una vez que un caso tiene su cupo completo de árbitros, se bloquea y comienza la fase de compromiso.
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
Compromete tu voto: dos veces
Dos votos de comprometer-revelar corren en paralelo: uno fuera de cadena que se registra en el propio rastro de auditoría y la reputación del caso, y uno en cadena contra la cuenta DisputeCase de openfiat-escrow que realmente decide el resultado ponderado por stake. Usa la misma decisión y la misma sal para ambos, pero el enum propio de cada lado, no el mismo número: Invalid fuera de cadena es 2, InvalidDispute en cadena es 3, porque MutualSettlement ocupa el 2 en cadena. Haz el hash del byte equivocado y te comprometes a algo que nunca podrás revelar, que es precisamente lo que hace que a un árbitro le apliquen slashing.
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
Revela cuando se abra la ventana
Revela tu resultado y tu sal en ambos lugares una vez que se cierre la ventana de compromiso. En cadena, aquí es también donde tu voto obtiene su peso real: la instrucción de revelado lee directamente tu cuenta de stake de rol Arbitrator, así que una billetera sin stake de árbitro sencillamente no puede aportar uno 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, ); - 06
El resultado se ejecuta solo
Una vez que todos los árbitros han revelado, o se cierra la ventana de revelado, cualquiera —tú, el comprador, el vendedor o un bot no relacionado— puede llamar a execute_dispute_outcome. Solo cuenta los votos ya registrados en cadena. El diseño paga entonces a la mayoría una parte de las comisiones del caso y toma parte del stake de quien reveló en contra, pero ni la recompensa ni la penalización están implementadas, así que hoy el recuento mueve los fondos de los traders y nada más.
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);