首页 > 其他分享 >世界杯竞猜项目Dapp-第五章(空投奖励)

世界杯竞猜项目Dapp-第五章(空投奖励)

时间:2022-12-25 15:02:46浏览次数:43  
标签:set 竞猜 get value Dapp BigInt let 空投 id

流程

  • 建立 WorldCup 合约(已完成)
  • 发行 WorldCupToken(已完成)
  • 统计玩家下注的历史,计算每个人分配多少(由 subgraph 链下统计)
  • 管理员分配奖励(一个合约)
  • 用户领取奖励

分配奖励分析

技术选型

使用 merkle tree 方式,对当期所有玩家进行统一设置,然后各自去 claim。merkleRoot 是一个 hash 值,根节点hash确定后,叶子节点和通向根节点路径中的 hash 值就都确定了,从而可以完成快速验证功能,能够满足我们的奖励方法需求。

实现思路

1 管理员分配每个玩家 token 数量,生成 merkleRoot 写入合约

  • 需要从 subgraph 请求 Play 玩家下注历史数据
  • 然后在本地(前端或脚本),按照空投策略(比如参与权重 1,猜中权重 2),生成 merkleRoot
  • 调用 token 奖励合约设置 merkleRoot 并发送事件,在 subgraph 计算每个玩家可以分配的 token 数量

2 玩家领取 token 奖励,需要将叶子信息和证明信息传递给合约,合约校验通过后,执行奖励发放

  • 需要从 subgraph 请求所有用户的奖励数据,生成 merkleRoot 以生成证明
  • 然后从 subgraph 请求自己能够获取的 token 数量
  • 调用奖励合约,领取奖励

具体逻辑


1 管理员调用 distribute(步骤 7),这个方法的核心参数是 merkleRoot,是由所有玩家的“地址+奖励数量”作为叶子结点生成的。为了得到这些叶子结点,我们需要向 subgraph 请求玩家的原始数据(步骤 8),然后根据奖励分配规则,在前端本地计算每个人分配的数量,进而生成 merkleRoot(步骤 9),设置到合约中。
2 存储 merkleRoot 后发出事件(步骤 10),subgraph 内部收到事件后,会再重复计算一次玩家奖励,并将计算结果存储在 subgraph 库中(reward list)
3 玩家发起领奖时(步骤 11),点击 ClaimReward,此时需要的参数为:玩家地址、奖励数量、证明,用于在合约内部验证 merkleRoot。这些数据在上一步已存储在 subgraph 中,所以玩家发起请求获取奖励列表(步骤 12),在本地计算证明 proof,然后传递给合约。
4 合约接收到玩家领奖请求时,会将当前玩家当成一个叶子节点,进而与已经设置好的 merkleRoot 进行验证。如果验证成功,则向玩家转账奖励,反之合约revert。

代码实现

分配奖励合约


编写运行脚本

import { ethers } from "hardhat";

async function main() {
    // FFTToken address
    let token = '0x8fe664FA864C61054D2dec3dEB54b204a427d8A8'

    const Distributor = await ethers.getContractFactory("WorldCupDistributor");
    const distributor = await Distributor.deploy(token);

    await distributor.deployed();

    console.log(`new distributor: ${distributor.address}`);
}

main().catch((error) => {
    console.error(error);
    process.exitCode = 1;
});

部署验证合约

npx hardhat run scripts/deployDistributor.ts --network goerli

# 0xF19233dFE30219F4D6200c02826B80e4347EF8BF

npx hardhat verify 0xF19233dFE30219F4D6200c02826B80e4347EF8BF 0x4c305227E762634CB7d3d9291e42b423eD45f1AD  --network goerli

subgraph 链下监听

首先像上一章那样初始化配置 subgraph 项目,将 WorldCup 和 WorldCupDistributor 合约部署进项目中,以对合约进行监听,然后修改配置文件 startBlock。接着在 schema 文件中重新创建我们需要的实体对象:

# 玩家 Player
type PlayRecord @entity {
  id: ID!
  index: BigInt! # uint256 第几期
  player: Bytes! # address
  selectCountry: BigInt! # uint256 选择的队伍
  time: BigInt!
  block: BigInt!
}

type NeedToHandle @entity {
  id: ID!
  list: [PlayRecord!]!
}

# 球队 winner
type FinializeHistory @entity {
  id: ID!
  result: BigInt!
}

# 玩家奖励详情(分配后)
type PlayerDistribution @entity {
  id: ID!
  index: BigInt!
  player: Bytes!
  rewardAmt: BigInt! # 玩家应得奖金
  weight: BigInt! # 权重
  isClaimed: Boolean! # 是否已领取
}

# 每一期的奖励记录
type RewardHistory @entity {
  id: ID!
  index: BigInt!
  rewardAmt: BigInt! # 当期奖池
  settleBlockNumber: BigInt!
  totalWeight: BigInt!
  list: [PlayerDistribution!]!
}

# 当期奖池
type MerkleDistributor @entity {
  id: ID!
  index: BigInt!
  totalAmt: BigInt!
  settleBlockNumber: BigInt!
}

注意对应的 /generated/schema.ts 临时也要修改一下,直接 copy 就行,该文件虽然是自动根据实体生成的。

// THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.

import {
  TypedMap,
  Entity,
  Value,
  ValueKind,
  store,
  Bytes,
  BigInt,
  BigDecimal
} from "@graphprotocol/graph-ts";

export class PlayRecord extends Entity {
  constructor(id: string) {
    super();
    this.set("id", Value.fromString(id));
  }

  save(): void {
    let id = this.get("id");
    assert(id != null, "Cannot save PlayRecord entity without an ID");
    if (id) {
      assert(
        id.kind == ValueKind.STRING,
        `Entities of type PlayRecord must have an ID of type String but the id '``{id.displayData()}' is of type ``{id.displayKind()}`
      );
      store.set("PlayRecord", id.toString(), this);
    }
  }

  static load(id: string): PlayRecord | null {
    return changetype<PlayRecord | null>(store.get("PlayRecord", id));
  }

  get id(): string {
    let value = this.get("id");
    return value!.toString();
  }

  set id(value: string) {
    this.set("id", Value.fromString(value));
  }

  get index(): BigInt {
    let value = this.get("index");
    return value!.toBigInt();
  }

  set index(value: BigInt) {
    this.set("index", Value.fromBigInt(value));
  }

  get player(): Bytes {
    let value = this.get("player");
    return value!.toBytes();
  }

  set player(value: Bytes) {
    this.set("player", Value.fromBytes(value));
  }

  get selectCountry(): BigInt {
    let value = this.get("selectCountry");
    return value!.toBigInt();
  }

  set selectCountry(value: BigInt) {
    this.set("selectCountry", Value.fromBigInt(value));
  }

  get time(): BigInt {
    let value = this.get("time");
    return value!.toBigInt();
  }

  set time(value: BigInt) {
    this.set("time", Value.fromBigInt(value));
  }

  get block(): BigInt {
    let value = this.get("block");
    return value!.toBigInt();
  }

  set block(value: BigInt) {
    this.set("block", Value.fromBigInt(value));
  }
}

export class NeedToHandle extends Entity {
  constructor(id: string) {
    super();
    this.set("id", Value.fromString(id));
  }

  save(): void {
    let id = this.get("id");
    assert(id != null, "Cannot save NeedToHandle entity without an ID");
    if (id) {
      assert(
        id.kind == ValueKind.STRING,
        `Entities of type NeedToHandle must have an ID of type String but the id '``{id.displayData()}' is of type ``{id.displayKind()}`
      );
      store.set("NeedToHandle", id.toString(), this);
    }
  }

  static load(id: string): NeedToHandle | null {
    return changetype<NeedToHandle | null>(store.get("NeedToHandle", id));
  }

  get id(): string {
    let value = this.get("id");
    return value!.toString();
  }

  set id(value: string) {
    this.set("id", Value.fromString(value));
  }

  get list(): Array<string> {
    let value = this.get("list");
    return value!.toStringArray();
  }

  set list(value: Array<string>) {
    this.set("list", Value.fromStringArray(value));
  }
}

export class FinializeHistory extends Entity {
  constructor(id: string) {
    super();
    this.set("id", Value.fromString(id));
  }

  save(): void {
    let id = this.get("id");
    assert(id != null, "Cannot save FinializeHistory entity without an ID");
    if (id) {
      assert(
        id.kind == ValueKind.STRING,
        `Entities of type FinializeHistory must have an ID of type String but the id '``{id.displayData()}' is of type ``{id.displayKind()}`
      );
      store.set("FinializeHistory", id.toString(), this);
    }
  }

  static load(id: string): FinializeHistory | null {
    return changetype<FinializeHistory | null>(
      store.get("FinializeHistory", id)
    );
  }

  get id(): string {
    let value = this.get("id");
    return value!.toString();
  }

  set id(value: string) {
    this.set("id", Value.fromString(value));
  }

  get result(): BigInt {
    let value = this.get("result");
    return value!.toBigInt();
  }

  set result(value: BigInt) {
    this.set("result", Value.fromBigInt(value));
  }
}

export class PlayerDistribution extends Entity {
  constructor(id: string) {
    super();
    this.set("id", Value.fromString(id));
  }

  save(): void {
    let id = this.get("id");
    assert(id != null, "Cannot save PlayerDistribution entity without an ID");
    if (id) {
      assert(
        id.kind == ValueKind.STRING,
        `Entities of type PlayerDistribution must have an ID of type String but the id '``{id.displayData()}' is of type ``{id.displayKind()}`
      );
      store.set("PlayerDistribution", id.toString(), this);
    }
  }

  static load(id: string): PlayerDistribution | null {
    return changetype<PlayerDistribution | null>(
      store.get("PlayerDistribution", id)
    );
  }

  get id(): string {
    let value = this.get("id");
    return value!.toString();
  }

  set id(value: string) {
    this.set("id", Value.fromString(value));
  }

  get index(): BigInt {
    let value = this.get("index");
    return value!.toBigInt();
  }

  set index(value: BigInt) {
    this.set("index", Value.fromBigInt(value));
  }

  get player(): Bytes {
    let value = this.get("player");
    return value!.toBytes();
  }

  set player(value: Bytes) {
    this.set("player", Value.fromBytes(value));
  }

  get rewardAmt(): BigInt {
    let value = this.get("rewardAmt");
    return value!.toBigInt();
  }

  set rewardAmt(value: BigInt) {
    this.set("rewardAmt", Value.fromBigInt(value));
  }

  get weight(): BigInt {
    let value = this.get("weight");
    return value!.toBigInt();
  }

  set weight(value: BigInt) {
    this.set("weight", Value.fromBigInt(value));
  }

  get isClaimed(): boolean {
    let value = this.get("isClaimed");
    return value!.toBoolean();
  }

  set isClaimed(value: boolean) {
    this.set("isClaimed", Value.fromBoolean(value));
  }
}

export class RewardHistory extends Entity {
  constructor(id: string) {
    super();
    this.set("id", Value.fromString(id));
  }

  save(): void {
    let id = this.get("id");
    assert(id != null, "Cannot save RewardHistory entity without an ID");
    if (id) {
      assert(
        id.kind == ValueKind.STRING,
        `Entities of type RewardHistory must have an ID of type String but the id '``{id.displayData()}' is of type ``{id.displayKind()}`
      );
      store.set("RewardHistory", id.toString(), this);
    }
  }

  static load(id: string): RewardHistory | null {
    return changetype<RewardHistory | null>(store.get("RewardHistory", id));
  }

  get id(): string {
    let value = this.get("id");
    return value!.toString();
  }

  set id(value: string) {
    this.set("id", Value.fromString(value));
  }

  get index(): BigInt {
    let value = this.get("index");
    return value!.toBigInt();
  }

  set index(value: BigInt) {
    this.set("index", Value.fromBigInt(value));
  }

  get rewardAmt(): BigInt {
    let value = this.get("rewardAmt");
    return value!.toBigInt();
  }

  set rewardAmt(value: BigInt) {
    this.set("rewardAmt", Value.fromBigInt(value));
  }

  get settleBlockNumber(): BigInt {
    let value = this.get("settleBlockNumber");
    return value!.toBigInt();
  }

  set settleBlockNumber(value: BigInt) {
    this.set("settleBlockNumber", Value.fromBigInt(value));
  }

  get totalWeight(): BigInt {
    let value = this.get("totalWeight");
    return value!.toBigInt();
  }

  set totalWeight(value: BigInt) {
    this.set("totalWeight", Value.fromBigInt(value));
  }

  get list(): Array<string> {
    let value = this.get("list");
    return value!.toStringArray();
  }

  set list(value: Array<string>) {
    this.set("list", Value.fromStringArray(value));
  }
}

export class MerkleDistributor extends Entity {
  constructor(id: string) {
    super();
    this.set("id", Value.fromString(id));
  }

  save(): void {
    let id = this.get("id");
    assert(id != null, "Cannot save MerkleDistributor entity without an ID");
    if (id) {
      assert(
        id.kind == ValueKind.STRING,
        `Entities of type MerkleDistributor must have an ID of type String but the id '``{id.displayData()}' is of type ``{id.displayKind()}`
      );
      store.set("MerkleDistributor", id.toString(), this);
    }
  }

  static load(id: string): MerkleDistributor | null {
    return changetype<MerkleDistributor | null>(
      store.get("MerkleDistributor", id)
    );
  }

  get id(): string {
    let value = this.get("id");
    return value!.toString();
  }

  set id(value: string) {
    this.set("id", Value.fromString(value));
  }

  get index(): BigInt {
    let value = this.get("index");
    return value!.toBigInt();
  }

  set index(value: BigInt) {
    this.set("index", Value.fromBigInt(value));
  }

  get totalAmt(): BigInt {
    let value = this.get("totalAmt");
    return value!.toBigInt();
  }

  set totalAmt(value: BigInt) {
    this.set("totalAmt", Value.fromBigInt(value));
  }

  get settleBlockNumber(): BigInt {
    let value = this.get("settleBlockNumber");
    return value!.toBigInt();
  }

  set settleBlockNumber(value: BigInt) {
    this.set("settleBlockNumber", Value.fromBigInt(value));
  }
}

接着编写监听事件逻辑,主要包括玩家下注信息的记录,以及调用 distributeReward() 后发出的事件(对 MerkleRoot 的再次计算存储)

import { Address, BigInt, Bytes, TypedMap, ethereum, log, bigInt } from "@graphprotocol/graph-ts";

import {
  WorldCup,
  ClaimReward,
  Finialize,
  Play
} from "../generated/WorldCup/WorldCup"

import {
  Claimed,
  DistributeReward
} from "../generated/WorldCupDistributor/WorldCupDistributor"

import { PlayRecord, NeedToHandle, PlayerDistribution, MerkleDistributor, FinializeHistory, RewardHistory } from "../generated/schema"

let NO_HANDLE_ID = "noHandleId"

export function handlePlay(event: Play): void {
  // 统计所有 play 事件(玩家下注),存储起来
  // 1.创建 id
  let id = event.params._player.toHex() + "#" + event.params._currRound.toString() + "#" + event.block.timestamp.toHex();

  // 2.创建玩家下注记录
  let entity = new PlayRecord(id);

  // 3.塞数据
  entity.index = BigInt.fromI32(event.params._currRound);
  entity.player = event.params._player;
  entity.selectCountry = BigInt.fromI32(event.params._country);
  entity.time = event.block.timestamp;
  entity.block = event.block.number;

  // 4.保存
  entity.save()

  // 5.保存待处理的玩家下注记录
  let noHandle = NeedToHandle.load(NO_HANDLE_ID);
  if (!noHandle) {
    noHandle = new NeedToHandle(NO_HANDLE_ID);
    noHandle.list = [];
  }

  noHandle.list.push(id)

  noHandle.save()
}

// 当期冠军球队记录
export function handleFinialize(event: Finialize): void {
  let id = event.params._currRound.toString();
  let entity = new FinializeHistory(id);

  entity.result = event.params._country;
  entity.save();
}

// 1 遍历本期所有的Play记录
// 2 计算每个玩家的权重
// 3 按照权重分配总奖励数
export function handleDistributeReward(event: DistributeReward): void {
  let id = event.params.index.toString();
  let rewardAmt = event.params.amount;
  // 第几期
  let index = event.params.index;
  let settleBlockNumber = event.params.settleBlockNumber;

  // 找到当期冠军球队是谁
  let winCountry = FinializeHistory.load(id)
  if (!winCountry) {
    return;
  }

  // 保存当期 token 奖励信息
  let merkleEntity = new MerkleDistributor(id);

  merkleEntity.index = index;
  merkleEntity.totalAmt = rewardAmt;
  merkleEntity.settleBlockNumber = settleBlockNumber;
  merkleEntity.save();

  let startBlock = BigInt.fromI32(0);
  let endBlock = settleBlockNumber;

  if (index > BigInt.fromI32(0)) {
    // 上一期的 id
    let prevId = index.minus(BigInt.fromI32(1)).toString()
    let prev = MerkleDistributor.load(prevId) as MerkleDistributor;
    startBlock = prev.settleBlockNumber;
  }

  let totalWeight = BigInt.fromI32(0)
  // 实发总奖励 由于精准度问题可能造成实际的奖励少了一点
  let rewardActuallyAmt = BigInt.fromI32(0)
  // 奖励历史
  let rewardHistoryList: string[] = [];

  let noHandle = NeedToHandle.load(NO_HANDLE_ID);
  if (noHandle) {
    // 玩家地址 => 奖励权重
    let group = new TypedMap<Bytes, BigInt>();
    // 当期所有玩家记录
    let currentList = noHandle.list;
    let newList: string[] = [];
    log.warning("current list: ", currentList)

    for (let i = 0; i < currentList.length; i++) {
      // 玩家奖励权重 初始化默认参与奖励权重 1
      let playerWeight = BigInt.fromI32(1)
      // 玩家下注记录
      let record = PlayRecord.load(currentList[i]) as PlayRecord;

      log.warning("record.block:", [record.block.toString()])
      log.warning("startBlock:", [startBlock.toString()])
      log.warning("endBlock:", [endBlock.toString()])

      if (record.block > startBlock && record.block <= endBlock) {
        if (winCountry.result == record.selectCountry) {
          // 猜对了就可以得到双倍奖励
          playerWeight = playerWeight.times(BigInt.fromI32(2))
        }

        let prevWeight = group.get(record.player)
        if (!prevWeight) {
          // 不存在默认 0
          prevWeight = BigInt.fromI32(0)
        }

        // 更新玩家奖励权重 可能下注多次 所以需要累加
        group.set(record.player, prevWeight.plus(playerWeight));

        // 更新总权重
        totalWeight = totalWeight.plus(playerWeight);
        log.warning("hello world totalWeight: ", [totalWeight.toString()])
      } else {
        // block 区间之外的 会添加到 newList 中
        newList.push(currentList[i]);
      }
    }

    // 按照权重给玩家分配奖励 存储到 PlayerDistribution(供最终调用)
    for (let j = 0; j < group.entries.length; j++) {
      let player = group.entries[j].key;
      let weight = group.entries[j].value;

      let id = player.toString() + "#" + index.toString()

      log.warning("totalWeight:", [totalWeight.toString()])
      // 计算奖励
      let reward = rewardAmt.times(weight).div(totalWeight);

      let playerDistribution = new PlayerDistribution(id);
      playerDistribution.index = index;
      playerDistribution.player = player;
      playerDistribution.rewardAmt = reward;
      playerDistribution.weight = weight;
      playerDistribution.isClaimed = false;
      playerDistribution.save();

      rewardHistoryList.push(id);
      rewardActuallyAmt = rewardActuallyAmt.plus(reward);
    }
    
    noHandle.list = newList;
    noHandle.save();
  }

  // 存储本期奖励详情 供后续查看历史
  let rewardHistory = new RewardHistory(id);
  rewardHistory.index = index;
  rewardHistory.rewardAmt = rewardAmt;
  rewardHistory.settleBlockNumber = settleBlockNumber;
  rewardHistory.totalWeight = totalWeight;
  rewardHistory.list = rewardHistoryList;
}

export function handleClaimed(event: Claimed): void {
}

export function handleClaimReward(event: ClaimReward): void {
}

先在 node_modules 目录下载所需依赖,接着编写脚本 scripts/distributeReward.ts 对第0期的所有玩家,发放10000 * 10^18 个奖励,读取数据,生成merkleRoot

npm install --save cross-fetch
npm i apollo-boost graphql react-apollo -S
npm install merkletreejs
import { ApolloClient, gql, HttpLink, InMemoryCache } from 'apollo-boost';
import { fetch } from 'cross-fetch';
import { BigNumber } from 'bignumber.js'
import { MerkleTree } from 'merkletreejs'
import hre from 'hardhat'
import { any } from 'hardhat/internal/core/params/argumentTypes';

// const graphUrl = process.env.SUBGRAPH_API;
// const graphUrl = "http://localhost:8000/subgraphs/name/duke/worldcup"
const graphUrl = "https://api.thegraph.com/subgraphs/name/dukedaily/worldcup"

async function executeQuery(query: string, variables: any) {
    const client = new ApolloClient({
        link: new HttpLink({uri: graphUrl, fetch}),
        cache: new InMemoryCache(),
    });

    return await client.query({
        query: gql(query),
        variables: variables,
    });
}

function calculatePlayerReward() {

}

async function getPlayerRecords(index: number) {
    const query = `{
        playRecords(where: {
            index: ${index}
        }){
            id
            index
            player
            selectCountry
            block
        }
    }`;

    let data = await executeQuery(query, {})
    return data['data']['playRecords']
}

async function getWinnerHistory(index: number) {
    const query = `{
        finializeHistory(id: ${index}) {
            result
        }
    }`;

    let data = await executeQuery(query, {})
    return data['data']['finializeHistory']
}

async function getPlayerDistributions(index: number) {
    const query = ` {
      playerDistributions(
        where : {
          index: ${index}
        }
      ) {
        player
        rewardAmt
        weight
      }
    }
    `;
  
    let data = await executeQuery(query, {})
    return data['data']['playerDistributions']
  }

  function getPlayerRewardList(totalReward: string, records: any, winner: number) {
    // 遍历所有的records,计算每个人的奖励数量,返回一个数组,然后抛出来,后续使用进行merkel计算
    let group = {}
    let totalWeight: string = '0'
  
    records.map((it: {
      player(arg0: string, player: any): unknown; selectCountry: number;
    }) => {
      // 猜中奖励翻倍
      // console.log('mapping it:', it);
      // console.log('it.selectCountry:', it.selectCountry, 'winner:', winner);
      let weight = (it.selectCountry === winner) ? 2 : 1
      return { it, weight }
    }).forEach((element: {
      weight(weight: any): unknown; player: string | number;
    }) => {
      let value = group[element.player] || {
        list: [],
        weight: '0'
      }
  
      // console.log('current value:', value);
  
      value.list.push(element.it)
      value.weight = new BigNumber(value.weight).plus(element.weight).toFixed()
      totalWeight = new BigNumber(totalWeight).plus(element.weight).toFixed()
  
      group[element.it.player] = value
    });
  
    console.log('group', group)
    console.log('totalWeight', totalWeight)
  
    let playerDistributionList = []
    let actuallyAmt = "0"
  
    for (const player in group) {
      const item = group[player];
  
      // TODO dp是什么?
      item.reward = new BigNumber(item.weight).multipliedBy(totalReward).div(totalWeight).dp(0, BigNumber.ROUND_DOWN).toFixed();
      actuallyAmt = new BigNumber(actuallyAmt).plus(item.reward).toFixed()
  
      // console.log('total reward: ', totalReward, 'item.weight:', item.weight);
      console.log('reward:', item.reward.toString());
      playerDistributionList.push({
        player: player,
        rewardAmt: item.reward
      })
    }
  
    return { playerDistributionList, actuallyAmt };
  }

  function generateLeaf(index: number, player: string, rewardAmt: number) {
    return hre.ethers.utils.keccak256(
      hre.ethers.utils.solidityPack(
        ['uint256', 'address', 'uint256'],
        [index, player, rewardAmt]
      ))
  }

  function generateMerkelTree(index: number, playerRewardList: any) {
    // make leafs
    let items = playerRewardList.map((it => {
      console.log('it.rewardAmt:', it.rewardAmt);
      return generateLeaf(index, it.player, it.rewardAmt);
    }))
  
    // create tree
    const tree = new MerkleTree(items, hre.ethers.utils.keccak256, { sort: true })
    return tree
  }

  export const oneEther = new BigNumber(Math.pow(10, 18))

  export const createBigNumber18 = (v: any) => {
    return new BigNumber(v).multipliedBy(oneEther).toFixed()
  }

const CURRENT_ROUND = 0;
const TOTAL_REWARD = createBigNumber18(10000);
const currentPlayer = '0xe8191108261f3234f1c2aca52a0d5c11795aef9e';

async function main() {
    // 查询 subgraph 获取玩家数据
    const playRecords = await getPlayerRecords(CURRENT_ROUND)
    
    const winner = await getWinnerHistory(CURRENT_ROUND)
    console.log(`winner for round ``{CURRENT_ROUND} is : ``{winner['result']}`);

    // 所有Player都会设置weight:1
    // 如果猜中了,weight: 2
    // 计算每个玩家的奖励
    const {playerDistributionList, actuallyAmt} = getPlayerRewardList(TOTAL_REWARD, playRecords, winner['resuly'])

    // 计算 MerkleRoot
    const tree = generateMerkelTree(CURRENT_ROUND, playerDistributionList)
    console.log('root:', tree.getHexRoot());

    //TODO 此时应该调用合约将 merkleRoot 设置进去了

    console.log('请手动发放奖励!')

    console.log('准备生成领取奖励所需数据:', currentPlayer);
    // 查询 subgraph 获取玩家奖励数据
    const playerDistributions = await getPlayerDistributions(CURRENT_ROUND)

    const newTree = generateMerkelTree(CURRENT_ROUND, playerDistributions)
    console.log('newRoot:', newTree.getHexRoot());
    
    const player = playerDistributions.filter(function (item: any) {
        return item.player === currentPlayer.toLowerCase()
    })[0]

    console.log('player:', player);

    const leaf = generateLeaf(CURRENT_ROUND, player.player, player.rewardAmt)
    const proof = newTree.getHexProof(leaf)
    console.log('proof:', proof);

    // TODO 使用 proof 作为合约入参进行验证发放奖励
}

main().catch(error => {
    console.error(error);
    process.exitCode = 1;
})

部署 subgraph

# 启动graphnode
docker-compose up

# 创建
npm run codegen
npm run build
npm run create-local
npm run deploy-local

# 分发奖励
npx hardhat run scripts/distributeReward.ts

标签:set,竞猜,get,value,Dapp,BigInt,let,空投,id
From: https://www.cnblogs.com/pandacode/p/17004030.html

相关文章

  • 【配置化】C# dapper是怎么实现的?精短ORM
    目录一、什么是dapper二、实现问题与思路&源码参考三、小结 一、什么是dapperdapper是个组件,一个dll文件,可以通过NuGet下载。作用:快速访问数据库并自动完成数据......
  • 链路追踪-Google-Dapper论文翻译总结
    ​一、两个基本需求无处不在的部署:无处不在很重要,如果系统的一小部分没有被监视,就会受到影响连续监测:因为通常情况下,不寻常或其他值得注意的系统行为很难或不可能重现最......
  • 世界杯竞猜项目Dapp-第四章(subgraph)
    subgraph是什么subgraph索引协议作为Dapp领域最重要的基建之一(如uniswap、wave等都在使用),主要用来做链上数据索引,即在链下对链上事件进行捕捉(扫链、计算、存储),然后......
  • 世界杯竞猜项目Dapp-第三章(ERC20)
    ERC20是标准的以太坊Token协议,它也是一个合约代码,只要在该合约内部实现了特定的6个方法,就会被系统判定为代币合约,具体总结为:6个必要接口,2个必要事件,3个可选接口,详......
  • C# .NET 从dapper调用存储过程,该存储过程接受用户定义表类型的列表【实战】
    dapper给存储过程传《用户自定义表类型》tabledapper给存储过程传用户自定义表类型tabledapper给存储过程传table存储过程​​InsertUserInfo​​​,该存储过程接受《用......
  • 泰山众筹dapp项目开发原理分析(源码搭建)
    泰山众筹”是一种以卖货为主的一种电商商业模式。众筹模式是商城快速驱动用户自我裂变的一种促销活动。通过用户主动发起链接人脉,好友互助的模式,以更低的门槛参与并完成项......
  • 如何用vue+免费的webdb 实现一个世界杯足球竞猜系统
    一、前言最近世界杯在如火如荼的进行。我们都知道,中国也派出了我们的一支强大的队伍:中国建筑队,全程参与了世界杯的所有比赛。哈哈开个玩笑,不过说到世界杯,还真有不少朋友,不......
  • DApp实战:开发属于你的第一个DApp - 我的日记本
    效果预览项目的视频教程部分已经发布到了b站​​https://space.bilibili.com/391924926/channel/seriesdetail?sid=2745034​​初始化状态添加删除开发环境准备系统环境Remi......
  • 世界杯竞猜项目Dapp-第一章(合约开发)
    前言最近卡塔尔世界杯如火如荼,让我们一起来尝试利用solidity语言做一个世界杯竞猜的Dapp实战项目,本次实战学习主要参考:https://github.com/dukedaily/solidity-expert......
  • .NET6之MiniAPI(二十六):封装Dapper
    在上一篇说过,Dapper是通过扩展IDbConnection来达到实现的,那带来的一个问题就是隔离性不好,比如在做单元测试时,mock就有点困难,所以在实践中,我对Dapper作了扩展,下面分享出......