Beraplug uses a public Dune dashboard to determine which wallets are eligible and how many points they have earned for a given round. A round is defined by a round ID number, a description of the round's prize, a start time and an end time.
An offchain script is used to hash the round's metadata and eligible wallets, use that hash as the seed for a random number generator, and output the winning address associated with the chosen number.
Independent verification
Because the process is deterministic based on the inputs, anyone can verify that the winner is accurate. Use the script below locally or on a hosted node.js environment like Replit. Replace roundId, roundStart, roundEnd, and prizeDescription with the exact values from any entry from the game log. Check that the winner you compute matches the one on the log, and check the transaction links to verify the prizes have been transferred.
Please note that SQL, which Dune is built on, is not 100% deterministic due to how it handles floating point arithmetic and parallelization. In our testing, we see about a 0.07% chance of getting a different result. To account for this unlikely event, we execute the Dune query for each round at least twice. If the same winner is selected both times, we go with that. If the winners are different, we run the round a third time and use the majority winner. This increases round certainty to 99.9951%. If you perform independent verification and get a different result, please do not draw any conclusions until you see that result across multiple Dune executions.
require('dotenv').config();const { parse } =require('csv-parse/sync');constcrypto=require('crypto');constseedrandom=require('seedrandom');// Replace this with your own Dune API keyconstmeta= {'x-dune-api-key': {{apiKey}}}constheader=newHeaders(meta);// Replace these with the metadata for the Brown Hole round you want to validateconstroundId= {{roundId}};constroundStart='{{roundStart}}';constroundEnd='{{roundEnd}}';constprizeDescription='{{prizeDescription}}';constparams= {'query_parameters': {'starttime': roundStart,'endtime': roundEnd }};constbody=JSON.stringify(params);asyncfunctionexecuteDuneQuery() {constexecuteUrl='https://api.dune.com/api/v1/query/3447423/execute';try {constexecute=awaitfetch(executeUrl, { method:'POST', headers: header, body: body });constexecuteResponse=awaitexecute.json();console.log('Dune query submitted:',executeResponse.execution_id);awaitpollForCompletion(executeResponse.execution_id); } catch(error) {console.log('Error: ', error); }}asyncfunctionpollForCompletion(executionId) {conststatusUrl=`https://api.dune.com/api/v1/execution/${executionId}/status`;try {let completed =false;while (!completed) {constresponse=awaitfetch(statusUrl, { method:'GET', headers: header });conststatusResponse=awaitresponse.json();if (statusResponse.state ==='QUERY_STATE_COMPLETED') { completed =true;console.log('Dune query completed.');awaitgetDuneResults(executionId); } else {console.log('Dune query still executing, waiting...');awaitnewPromise(resolve =>setTimeout(resolve,5000)); // Wait for 5 seconds before polling again } } } catch (error) {console.error('Error polling for completion:', error); }}asyncfunctiongetDuneResults(executionId) {constresultsUrl=`https://api.dune.com/api/v1/execution/${executionId}/results/csv`;try {constdataResponse=awaitfetch(resultsUrl, { method:'GET', headers: header });constdata=awaitdataResponse.text();awaitflattenPoints(data); } catch (error) {console.log('Error getting results:', error); }}asyncfunctionflattenPoints(data) {constrecords=parse(data, { columns:true, skip_empty_lines:true });let flatList = [];let eligibleWallets = [];records.forEach(record => {eligibleWallets.push(record.wallet);for (let i =0; i <parseInt(record.points,10); i++) {flatList.push(record.wallet); } });constflatListString=flatList.join('\n');constconcatenatedString=`${roundId}\n${prizeDescription}\n${roundStart}\n${roundEnd}\n${eligibleWallets}`;consthash=crypto.createHash('sha256').update(concatenatedString).digest('hex');console.log('Hashed round data:', hash);constrng=seedrandom(hash);constrandomValue=rng();console.log('Random number generated using hash as seed:', randomValue);constrandomIndex=Math.floor(randomValue *flatList.length);console.log('Index corresponding to random number:', randomIndex);constwinningAddress= flatList[randomIndex];console.log(`Winning address at chosen index: ${winningAddress}`);console.log('--------------------');console.log(`${winningAddress} wins ${prizeDescription} for Brown Hole round ${roundId} based on points tallied between ${roundStart} and ${roundEnd}`);}executeDuneQuery();