Стать арбитром OpenFiat
Арбитры решают споры, рискуя собственным OPEN, а не репутацией. Здесь разбираются бонд, поиск дела и подача голоса «сначала коммит, потом раскрытие», который и приносит выплату.
Что вам нужно
- Кошелёк Solana как минимум с 10 000 OPEN — min_stake_arbitrator из развёрнутой конфигурации стейкинга, который управление может изменить — внесите бонд через страницу Stake в openfiat-app или по инструкциям ниже, если вы строите собственный клиент
- Кошелёк, способный подписывать сообщения, что делает любой релевантный кошелёк Solana — страница Arbitrate в openfiat-app проводит всё дело в браузере, а SDK доступны, если вы предпочитаете программировать
- Сетевой доступ к JSON-RPC-эндпоинту хотя бы одного узла OpenFiat, вашего или публичного
Работа над делом
Выполняйте эти шаги по порядку, по одному делу за раз. Каждый шаг приводит нужную команду или код.
- 01
Внесите бонд в OPEN, чтобы разблокировать арбитражный пул
Арбитры должны застейкать, прежде чем смогут увидеть доказательства по одному делу — это делает подкуп одного из них бесполезным (вы не знаете, какое дело нацелить) и даёт сети то, что можно слэшить, если вы проголосуете против раскрытого консенсуса. Развёрнутая конфигурация devnet задаёт минимум арбитра в 10 000 OPEN, в десять раз больше 1000, которые вносит каждая другая роль.
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
Найдите открытое дело
Арбитры выбирают, над какими спорами работать — вам никого не назначают. Запросите любой узел о делах, ещё не набравших требуемое число арбитров.
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
Присоединитесь, прежде чем сможете увидеть доказательства
Присоединение — это то, что разблокирует дело для вас: собственные материалы покупателя и продавца, подтверждения платежей и лог сообщений их сделки. Как только у дела есть полный состав арбитров, оно блокируется, и начинается фаза коммита.
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
Закоммитьте голос — дважды
Два голосования «коммит-раскрытие» идут бок о бок: одно офчейн, записываемое в собственный аудит-след и репутацию дела, и одно ончейн против аккаунта DisputeCase из openfiat-escrow, которое и решает исход, взвешенный по стейку. Используйте одно и то же решение и один и тот же salt для обоих — но собственный 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); - 05
Раскройте, как только откроется окно
Раскройте свой исход и salt в обоих местах, как только окно коммита закроется. Ончейн здесь же ваш голос обретает свой реальный вес: инструкция reveal читает ваш аккаунт стейка роли 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, ); - 06
Исход исполняется сам
Как только каждый арбитр раскрыл или окно раскрытия закрылось, любой — вы, покупатель, продавец или посторонний бот — может вызвать execute_dispute_outcome. Он считает только голоса, уже записанные ончейн. Затем схема платит большинству долю комиссий по делу и списывает часть стейка у тех, кто раскрыл против него — но ни вознаграждение, ни штраф не реализованы, поэтому сегодня подсчёт двигает средства трейдеров и ничего больше.
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);