Lewati ke konten

Menjadi arbiter OpenFiat

Arbiter memutus sengketa dengan OPEN mereka sendiri dipertaruhkan, bukan reputasi. Ini menelusuri bond, menemukan sebuah kasus, dan memberikan suara commit-lalu-reveal yang benar-benar membayar.

Yang Anda butuhkan

  • Sebuah dompet Solana dengan setidaknya 10.000 OPEN — min_stake_arbitrator dari konfigurasi staking yang di-deploy, yang dapat diubah tata kelola — bond ia lewat halaman Stake di openfiat-app, atau lewat instruksi di bawah jika Anda membangun klien Anda sendiri
  • Sebuah dompet yang dapat menandatangani pesan, yang dilakukan setiap dompet Solana yang relevan — halaman Arbitrate di openfiat-app menjalankan seluruh kasus di browser, dan SDK tersedia jika Anda lebih suka memprogram
  • Akses jaringan ke endpoint JSON-RPC setidaknya satu node OpenFiat, milik Anda atau yang publik

Menangani sebuah kasus

Kerjakan langkah-langkah ini secara berurutan, satu kasus setiap kali. Tiap langkah membawa perintah atau kode yang dibutuhkannya.

  1. 01

    Bond OPEN untuk membuka kunci pool arbitrase

    Arbiter harus men-stake sebelum dapat melihat bukti satu kasus — itulah yang membuat menyuap salah satunya sia-sia (Anda tak tahu kasus mana yang harus dibidik) dan memberi jaringan sesuatu untuk di-slash jika Anda memilih melawan konsensus terungkap. Konfigurasi devnet yang di-deploy menetapkan minimum arbiter di 10.000 OPEN, sepuluh kali 1.000 yang disetor setiap peran lain.

    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

    Temukan sebuah kasus terbuka

    Arbiter memilih sengketa mana yang dikerjakan — tak ada yang menugaskan satu kepada Anda. Kueri node mana pun untuk kasus yang belum mencapai jumlah arbiter yang diwajibkan.

    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

    Bergabung sebelum dapat melihat bukti

    Bergabung adalah yang membuka kunci sebuah kasus bagi Anda: pengajuan milik pembeli dan penjual sendiri, konfirmasi pembayaran, dan log pesan transaksi mereka. Begitu sebuah kasus memiliki jumlah arbiter lengkapnya, ia terkunci dan fase commit dimulai.

    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

    Komitkan suara Anda — dua kali

    Dua pemungutan suara commit-reveal berjalan berdampingan: satu off-chain yang tercatat di jejak audit dan reputasi milik kasus itu sendiri, dan satu on-chain terhadap akun DisputeCase milik openfiat-escrow yang benar-benar memutuskan hasil yang dibobot stake. Gunakan keputusan dan salt yang sama untuk keduanya — tetapi enum khusus tiap sisi, bukan angka yang sama: Invalid off-chain adalah 2, InvalidDispute on-chain adalah 3, karena MutualSettlement menempati 2 on-chain. Hash byte yang salah dan Anda berkomitmen pada sesuatu yang tak pernah dapat Anda ungkap, yang persis membuat seorang arbiter di-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

    Ungkap begitu jendelanya terbuka

    Ungkap hasil dan salt Anda di kedua tempat begitu jendela commit tertutup. On-chain, di sinilah pula suara Anda memperoleh bobot sejatinya: instruksi reveal membaca akun stake peran Arbitrator Anda secara langsung, jadi dompet tanpa stake arbiter tak dapat memberikan yang valid.

    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

    Hasilnya mengeksekusi dirinya sendiri

    Begitu tiap arbiter mengungkap, atau jendela reveal tertutup, siapa pun — Anda, pembeli, penjual, atau bot yang tak terkait — dapat memanggil execute_dispute_outcome. Ia hanya menghitung suara yang sudah tercatat on-chain. Desainnya lalu membayar mayoritas sebagian biaya kasus dan menyita sebagian stake dari yang mengungkap melawannya — tetapi baik imbalan maupun penalti belum diimplementasikan, jadi hari ini penghitungan menggerakkan dana trader dan tak lebih.

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

Arbiter tak pernah memegang dana transaksi. Sebuah keputusan hanya menggerakkan uang yang telah dikunci program kustodi itu sendiri sebelum sengketa dibuka.