From e4fbaf0775cee47409447e8e2ec05764d539a688 Mon Sep 17 00:00:00 2001 From: KONFeature Date: Wed, 3 Jul 2024 22:04:45 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20reward=20distribution=20index?= =?UTF-8?q?ing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ponder-env.d.ts | 2 +- ponder.schema.ts | 49 ++++++++++++++++++ src/campaignReward.ts | 112 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 1 deletion(-) create mode 100644 src/campaignReward.ts diff --git a/ponder-env.d.ts b/ponder-env.d.ts index 1d5dc7e..f8e7347 100644 --- a/ponder-env.d.ts +++ b/ponder-env.d.ts @@ -1,7 +1,7 @@ // This file enables type checking and editor autocomplete for this Ponder project. // After upgrading, you may find that changes have been made to this file. // If this happens, please commit the changes. Do not manually edit this file. -// See https://ponder.sh/docs/guides/typescript for more information. +// See https://ponder.sh/docs/getting-started/installation#typescript for more information. declare module "@/generated" { import type { Virtual } from "@ponder/core"; diff --git a/ponder.schema.ts b/ponder.schema.ts index a44d331..8998dbe 100644 --- a/ponder.schema.ts +++ b/ponder.schema.ts @@ -9,6 +9,7 @@ export default createSchema((p) => ({ // The linked webauthn validators validators: p.many("MultiWebAuthNValidator.accountId"), }), + // Validator we are tracking MultiWebAuthNValidator: p.createTable({ // Id is a concatenation of chain + account address @@ -142,4 +143,52 @@ export default createSchema((p) => ({ }), PressEventType: p.createEnum(["OPEN_ARTICLE", "READ_ARTICLE", "REFERRED"]), + + // Rewards related stuff + RewardingContract: p.createTable({ + // Address of the rewarding contract + id: p.hex(), + + // Address of the token that will be distributed + token: p.hex(), + + // All the rewards + rewards: p.many("Reward.contractId"), + }), + Reward: p.createTable({ + id: p.string(), // reward contract + user + + contractId: p.hex().references("RewardingContract.id"), + contract: p.one("contractId"), + + user: p.hex(), + + pendingAmount: p.bigint(), + totalAmount: p.bigint(), + + rewardAddedEvents: p.many("RewardAddedEvent.rewardId"), + rewardClaimedEvents: p.many("RewardClaimedEvent.rewardId"), + }), + RewardAddedEvent: p.createTable({ + id: p.string(), + + rewardId: p.string().references("Reward.id"), + reward: p.one("rewardId"), + + amount: p.bigint(), + + txHash: p.hex(), + timestamp: p.bigint(), + }), + RewardClaimedEvent: p.createTable({ + id: p.string(), + + rewardId: p.string().references("Reward.id"), + reward: p.one("rewardId"), + + amount: p.bigint(), + + txHash: p.hex(), + timestamp: p.bigint(), + }), })); diff --git a/src/campaignReward.ts b/src/campaignReward.ts new file mode 100644 index 0000000..c204c80 --- /dev/null +++ b/src/campaignReward.ts @@ -0,0 +1,112 @@ +import { type Context, ponder } from "@/generated"; +import type { Address } from "viem"; +import { referralCampaignAbi } from "../abis/frak-campaign-abis"; + +ponder.on("Campaigns:RewardAdded", async ({ event, context }) => { + const { Reward, RewardAddedEvent } = context.db; + + // Try to find a rewarding contract for the given event emitter + const rewardingContract = await getRewardingContract({ + contract: event.log.address, + context, + }); + + // Update the current user reward (insert it if not found) + const rewardId = `${event.log.address}-${event.args.user}`; + await Reward.upsert({ + id: rewardId, + create: { + contractId: rewardingContract.id, + user: event.args.user, + pendingAmount: event.args.amount, + totalAmount: event.args.amount, + }, + update: ({ current }) => ({ + pendingAmount: current.pendingAmount + event.args.amount, + totalAmount: current.totalAmount + event.args.amount, + }), + }); + + // Insert the reward event + await RewardAddedEvent.create({ + id: event.log.id, + data: { + rewardId, + amount: event.args.amount, + txHash: event.log.transactionHash, + timestamp: event.block.timestamp, + }, + }); +}); + +ponder.on("Campaigns:RewardClaimed", async ({ event, context }) => { + const { Reward, RewardClaimedEvent } = context.db; + + // Try to find a rewarding contract for the given event emitter + const rewardingContract = await getRewardingContract({ + contract: event.log.address, + context, + }); + + // Update the current user reward (insert it if not found) + const rewardId = `${event.log.address}-${event.args.user}`; + await Reward.upsert({ + id: rewardId, + create: { + contractId: rewardingContract.id, + user: event.args.user, + pendingAmount: -event.args.amount, + totalAmount: 0n, + }, + update: ({ current }) => ({ + pendingAmount: current.pendingAmount - event.args.amount, + }), + }); + + // Insert the reward event + await RewardClaimedEvent.create({ + id: event.log.id, + data: { + rewardId, + amount: event.args.amount, + txHash: event.log.transactionHash, + timestamp: event.block.timestamp, + }, + }); +}); + +/** + * Get the rewarding contract for the given event emitter + * @param contract + * @param context + */ +async function getRewardingContract({ + contract, + context, +}: { + contract: Address; + context: Context; +}) { + const { RewardingContract } = context.db; + // Try to find a rewarding contract for the given event emitter + let rewardingContract = await RewardingContract.findUnique({ + id: contract, + }); + if (!rewardingContract) { + // If not found, find the token of this campaign + const { token } = await context.client.readContract({ + abi: referralCampaignAbi, + address: contract, + functionName: "getConfig", + }); + + rewardingContract = await RewardingContract.create({ + id: contract, + data: { + token, + }, + }); + } + + return rewardingContract; +}