تخطَّ إلى المحتوى

كن مُحكِّم OpenFiat

يحسم المُحكِّمون النزاعات وOPEN الخاص بهم في خطر، لا سمعتهم. يمرّ هذا عبر الرهن وإيجاد قضية وإلقاء تصويت الالتزام-ثم-الكشف الذي يدفع فعلًا.

ما تحتاجه

  • محفظة Solana تحمل 10,000 OPEN على الأقل — min_stake_arbitrator في إعداد الرهن المنشور، الذي يمكن للحوكمة تغييره — ارهنها عبر صفحة Stake في openfiat-app، أو التعليمات أدناه إن كنت تبني عميلك الخاص
  • محفظة تستطيع توقيع الرسائل، وهو ما تفعله كل محفظة Solana كبرى — صفحة Arbitrate في openfiat-app تشغّل القضية كاملة في المتصفّح، وحِزم SDK موجودة إن فضّلت البرمجة
  • وصول شبكي إلى نقطة نهاية JSON-RPC لعُقدة OpenFiat واحدة على الأقل، لك أو عامة

العمل على قضية

اعمل عليها بالترتيب، قضية واحدة في كل مرة. كل خطوة تحمل الأمر أو الكود الذي تحتاجه.

  1. 01

    ارهن OPEN لفتح صندوق التحكيم

    على المُحكِّمين الرهن قبل رؤية أدلة قضية واحدة — هذا ما يجعل رشوة أحدهم بلا جدوى (لا تعرف أي قضية تستهدف) ويعطي الشبكة ما تقتطعه إن صوّتّ ضد الإجماع المكشوف. يضع إعداد devnet المنشور حدّ المُحكِّم الأدنى عند 10,000 OPEN، عشرة أضعاف الـ 1,000 التي يودعها كل دور آخر.

    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

    اعثر على قضية مفتوحة

    يختار المُحكِّمون أي النزاعات يعملون عليها — لا أحد يسنِد إليك واحدة. استعلم أي عُقدة عن القضايا التي ما زالت دون عددها المطلوب من المُحكِّمين.

    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

    انضم قبل أن ترى الأدلة

    الانضمام هو ما يفتح لك قضية: تقديمات المشتري والبائع، وتأكيدات الدفع، وسجل رسائل صفقتهما. بمجرد أن تبلغ قضية عددها الكامل من المُحكِّمين، تُقفل وتبدأ مرحلة الالتزام.

    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

    التزم بتصويتك — مرتين

    يجري تصويتا التزام-كشف جنبًا إلى جنب: واحد خارج السلسلة يُسجَّل في مسار التدقيق والسمعة الخاص بالقضية، وواحد على السلسلة مقابل حساب DisputeCase الخاص بـ openfiat-escrow الذي يحسم فعلًا النتيجة المرجّحة بالرهن. استخدم القرار نفسه والملح نفسه لكليهما — لكن enum الخاص بكل جانب، لا الرقم نفسه: Invalid خارج السلسلة هو 2، وInvalidDispute على السلسلة هو 3، لأن MutualSettlement يجلس عند 2 على السلسلة. جزّئ البايت الخاطئ فتلتزم بشيء لا تستطيع كشفه أبدًا، وهو بالضبط ما يجعل المُحكِّم يُقتطَع منه.

    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

    اكشف بمجرد أن تُفتَح النافذة

    اكشف نتيجتك وملحك في المكانين بمجرد أن تُغلَق نافذة الالتزام. على السلسلة، هنا أيضًا يحصل تصويتك على وزنه الحقيقي: تقرأ تعليمة الكشف حساب رهن دور Arbitrator الخاص بك مباشرة، فمحفظة بلا رهن مُحكِّم ببساطة لا تستطيع تقديم واحد صالح.

    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

    تنفّذ النتيجة نفسها

    بمجرد أن يكشف كل مُحكِّم، أو تُغلَق نافذة الكشف، يستطيع أي شخص — أنت أو المشتري أو البائع أو bot غير ذي صلة — استدعاء execute_dispute_outcome. لا يعدّ سوى الأصوات المسجّلة أصلًا على السلسلة. يدفع التصميم بعدها للأغلبية حصة من رسوم القضية ويأخذ جزءًا من رهن من كشف ضدها — لكن لا المكافأة ولا العقوبة منفّذتان، فاليوم يحرّك العدّ أموال المتداولين ولا شيء آخر.

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

لا يحتفظ المُحكِّمون أبدًا بأموال الصفقات. الحكم لا يحرّك سوى مال قفله برنامج الضمان نفسه قبل فتح النزاع.