From d5a3b5621e886e8c9117ed5fd4e4953597c4c4ce Mon Sep 17 00:00:00 2001 From: motechFR Date: Sun, 10 Nov 2024 19:07:12 +0200 Subject: [PATCH] Add weekly claims model (#385) * Add weekly claims model * ci: version bump to 0.93.1-rc-weekly-claims.0 * Add onchain claims * ci: version bump to 0.93.2-rc-weekly-claims.0 * Relocate protocol eas and merkle proofs to core * ci: version bump to 0.94.1-rc-weekly-claims.0 * Fix encoder * Fix userRefUID typing * ci: version bump to 0.94.1-rc-weekly-claims.1 * Bump version * ci: version bump to 0.94.1-rc-weekly-claims.3 * Fix type for merkle tree proofs return type * ci: version bump to 0.94.1-rc-weekly-claims.4 * Cleanup schema definitions * ci: version bump to 0.94.1-rc-weekly-claims.5 * Add encoding for name schema args * ci: version bump to 0.94.1-rc-weekly-claims.6 * Fix encoder for name schema * ci: version bump to 0.94.1-rc-weekly-claims.7 * Add onchain scout profile * ci: version bump to 0.94.1-rc-weekly-claims.8 * Rename the schema * Fix build * Fix schema name * ci: version bump to 0.94.1-rc-weekly-claims.9 * ci: version bump to 0.94.1-rc-weekly-claims.10 * Resync prisma with main branch * ci: version bump to 0.95.1-rc-weekly-claims.0 --------- Co-authored-by: Automated Version Bump --- package.json | 10 +- src/lib/protocol/easSchemas/constants.ts | 25 +++ .../easSchemas/contributionReceiptSchema.ts | 62 +++++++ src/lib/protocol/easSchemas/index.ts | 10 ++ .../easSchemas/scoutGameUserProfileSchema.ts | 40 +++++ src/lib/protocol/easSchemas/types.ts | 4 + .../proofs/__tests__/verifyClaim.spec.ts | 70 ++++++++ src/lib/protocol/proofs/merkleTree.ts | 37 +++++ .../migration.sql | 68 ++++++++ src/prisma/schema.prisma | 153 +++++++++++------- src/protocol.ts | 2 + 11 files changed, 423 insertions(+), 58 deletions(-) create mode 100644 src/lib/protocol/easSchemas/constants.ts create mode 100644 src/lib/protocol/easSchemas/contributionReceiptSchema.ts create mode 100644 src/lib/protocol/easSchemas/index.ts create mode 100644 src/lib/protocol/easSchemas/scoutGameUserProfileSchema.ts create mode 100644 src/lib/protocol/easSchemas/types.ts create mode 100644 src/lib/protocol/proofs/__tests__/verifyClaim.spec.ts create mode 100644 src/lib/protocol/proofs/merkleTree.ts create mode 100644 src/prisma/migrations/20241107150504_onchain_claims/migration.sql create mode 100644 src/protocol.ts diff --git a/package.json b/package.json index 55634891..5fb82af0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@charmverse/core", - "version": "0.95.0", + "version": "0.95.1-rc-weekly-claims.0", "description": "Core API for Charmverse", "type": "commonjs", "types": "./dist/cjs/index.d.ts", @@ -65,6 +65,10 @@ "./http": { "import": "./dist/esm/http.js", "require": "./dist/cjs/http.js" + }, + "./protocol": { + "import": "./dist/esm/protocol.js", + "require": "./dist/cjs/protocol.js" } }, "files": [ @@ -142,19 +146,21 @@ }, "dependencies": { "@datadog/browser-logs": "^4.42.2", + "@ethereum-attestation-service/eas-sdk": "^0.29.1", "@prisma/client": "^5.7.1", "async-sema": "^3.1.1", "fetch-retry": "^5.0.6", "lodash": "^4.17.21", "loglevel": "^1.8.1", "luxon": "^3.3.0", + "merkletreejs": "^0.4.0", "nanoid": "^3.3.6", "nanoid-dictionary": "^3.0.0", "prisma": "^5.7.1", "undici": "^6.19.5", "utf-8-validate": "^5.0.10", "uuid": "^9.0.0", - "viem": "^1.18.3" + "viem": "^2.21.42" }, "license": "GPLv3" } diff --git a/src/lib/protocol/easSchemas/constants.ts b/src/lib/protocol/easSchemas/constants.ts new file mode 100644 index 00000000..cbf95da5 --- /dev/null +++ b/src/lib/protocol/easSchemas/constants.ts @@ -0,0 +1,25 @@ +import { getSchemaUID, SchemaEncoder } from '@ethereum-attestation-service/eas-sdk'; + +export const NULL_EAS_REF_UID = '0x0000000000000000000000000000000000000000000000000000000000000000'; + +export const NULL_EVM_ADDRESS = '0x0000000000000000000000000000000000000000'; + +// This allows us to encode the schemaId and name of a name schema attestation +// Obtained from https://github.com/ethereum-attestation-service/eas-contracts/blob/558250dae4cb434859b1ac3b6d32833c6448be21/deploy/scripts/000004-name-initial-schemas.ts#L10C1-L11C1 +export const NAME_SCHEMA_DEFINITION = 'bytes32 schemaId,string name'; + +export const NAME_SCHEMA_UID = getSchemaUID(NAME_SCHEMA_DEFINITION, NULL_EVM_ADDRESS, true) as `0x${string}`; + +export type NameSchemaAttestation = { + schemaId: `0x${string}`; + name: string; +}; + +export function encodeNameSchemaAttestation({ name, schemaId }: NameSchemaAttestation): `0x${string}` { + const encoder = new SchemaEncoder(NAME_SCHEMA_DEFINITION); + + return encoder.encodeData([ + { name: 'schemaId', type: 'bytes32', value: schemaId }, + { name: 'name', type: 'string', value: name } + ]) as `0x${string}`; +} diff --git a/src/lib/protocol/easSchemas/contributionReceiptSchema.ts b/src/lib/protocol/easSchemas/contributionReceiptSchema.ts new file mode 100644 index 00000000..d40823e4 --- /dev/null +++ b/src/lib/protocol/easSchemas/contributionReceiptSchema.ts @@ -0,0 +1,62 @@ +import { SchemaEncoder } from '@ethereum-attestation-service/eas-sdk'; +import type { EASSchema } from 'protocol'; + +const contributionReceiptEASSchema = + 'bytes32 userRefUID,string description,string url,string metadataUrl,uint256 value,string type'; + +const contributionReceiptSchemaName = 'Contribution Receipt'; + +export const contributionSchemaDefinition: EASSchema = { + schema: contributionReceiptEASSchema, + name: contributionReceiptSchemaName +}; + +export type ContributionReceiptAttestation = { + userRefUID: `0x${string}`; + description: string; + url: string; + metadataUrl: string; + value: number; + type: string; +}; + +const encoder = new SchemaEncoder(contributionReceiptEASSchema); + +export function encodeContributionReceiptAttestation(attestation: ContributionReceiptAttestation): `0x${string}` { + const encodedData = encoder.encodeData([ + { name: 'userRefUID', type: 'bytes32', value: attestation.userRefUID }, + { name: 'description', type: 'string', value: attestation.description }, + { + name: 'url', + type: 'string', + value: attestation.url + }, + { + name: 'metadataUrl', + type: 'string', + value: attestation.metadataUrl + }, + { name: 'value', type: 'uint256', value: attestation.value }, + { name: 'type', type: 'string', value: attestation.type } + ]); + + return encodedData as `0x${string}`; +} + +export function decodeContributionReceiptAttestation(rawData: string): ContributionReceiptAttestation { + const parsed = encoder.decodeData(rawData); + const values = parsed.reduce((acc, item) => { + const key = item.name as keyof ContributionReceiptAttestation; + + if (key === 'value') { + acc[key] = parseInt(item.value.value as string); + } else if (key === 'userRefUID') { + acc[key] = item.value.value as `0x${string}`; + } else { + acc[key] = item.value.value as string; + } + return acc; + }, {} as ContributionReceiptAttestation); + + return values as ContributionReceiptAttestation; +} diff --git a/src/lib/protocol/easSchemas/index.ts b/src/lib/protocol/easSchemas/index.ts new file mode 100644 index 00000000..93d8e52f --- /dev/null +++ b/src/lib/protocol/easSchemas/index.ts @@ -0,0 +1,10 @@ +import { contributionSchemaDefinition } from './contributionReceiptSchema'; +import { scoutGameUserProfileSchemaDefinition } from './scoutGameUserProfileSchema'; +import type { EASSchema } from './types'; + +export * from './constants'; +export * from './contributionReceiptSchema'; +export * from './scoutGameUserProfileSchema'; +export * from './types'; + +export const allSchemas: EASSchema[] = [contributionSchemaDefinition, scoutGameUserProfileSchemaDefinition]; diff --git a/src/lib/protocol/easSchemas/scoutGameUserProfileSchema.ts b/src/lib/protocol/easSchemas/scoutGameUserProfileSchema.ts new file mode 100644 index 00000000..93cae30d --- /dev/null +++ b/src/lib/protocol/easSchemas/scoutGameUserProfileSchema.ts @@ -0,0 +1,40 @@ +import { SchemaEncoder } from '@ethereum-attestation-service/eas-sdk'; +import type { EASSchema } from 'protocol'; + +const scoutGameUserProfileEASSchema = 'string id,string metadataUrl'; + +const scoutGameUserProfileSchemaName = 'Scout Game User Profile'; + +export const scoutGameUserProfileSchemaDefinition: EASSchema = { + schema: scoutGameUserProfileEASSchema, + name: scoutGameUserProfileSchemaName +}; + +export type ScoutGameUserProfileAttestation = { + id: string; + metadataUrl: string; +}; + +const encoder = new SchemaEncoder(scoutGameUserProfileEASSchema); + +export function encodeScoutGameUserProfileAttestation(attestation: ScoutGameUserProfileAttestation): `0x${string}` { + const encodedData = encoder.encodeData([ + { name: 'id', type: 'string', value: attestation.id }, + { name: 'metadataUrl', type: 'string', value: attestation.metadataUrl } + ]); + + return encodedData as `0x${string}`; +} + +export function decodeScoutGameUserProfileAttestation(rawData: string): ScoutGameUserProfileAttestation { + const parsed = encoder.decodeData(rawData); + const values = parsed.reduce((acc, item) => { + const key = item.name as keyof ScoutGameUserProfileAttestation; + + acc[key] = item.value.value as string; + + return acc; + }, {} as ScoutGameUserProfileAttestation); + + return values as ScoutGameUserProfileAttestation; +} diff --git a/src/lib/protocol/easSchemas/types.ts b/src/lib/protocol/easSchemas/types.ts new file mode 100644 index 00000000..1b9611e5 --- /dev/null +++ b/src/lib/protocol/easSchemas/types.ts @@ -0,0 +1,4 @@ +export type EASSchema = { + schema: string; + name: string; +}; diff --git a/src/lib/protocol/proofs/__tests__/verifyClaim.spec.ts b/src/lib/protocol/proofs/__tests__/verifyClaim.spec.ts new file mode 100644 index 00000000..9dbd91a6 --- /dev/null +++ b/src/lib/protocol/proofs/__tests__/verifyClaim.spec.ts @@ -0,0 +1,70 @@ +import { generateMerkleTree, getMerkleProofs, verifyMerkleClaim, type ProvableClaim } from '../merkleTree'; + +const claimsInput: ProvableClaim[] = [ + { + // Key here so we can copy to other tests: 57b7b9b29419b66ac8156f844a7b0eb18d94f729699b3f15a3d8817d3f5980a3 + address: '0x3F2A655d4e39E6c4470703e1063e9a843586886A', + amount: 100 + }, + { + // Key here so we can copy to other tests: aa03d22263ff3e4df4105a20d08f62873f5e100974862fdc1f99083ba11e6adc + address: '0x2Fe1B8C9C8722f0D3e5B9a9D4115559bB8f04931', + amount: 200 + }, + { + // Key here so we can copy to other tests: c674865dde0163f480f818a78fc4d316c64d60b05666600734df8e8f37147f64 + address: '0x03F8B139fF6dbbb7475bAA5A71c16fcDD9495cc4', + amount: 300 + }, + { + address: '0x36446eF671954753801f9d73C415a80C0e550b32', + amount: 400 + }, + { + address: '0xD02953857250D32EC72064d9E2320B43296E52C0', + amount: 500 + } +]; + +describe('verifyMerkleClaim', () => { + it('should return true if the claim is valid', () => { + const { tree, rootHash } = generateMerkleTree(claimsInput); + const claim = claimsInput[0]; + const proofs = getMerkleProofs(tree, claim); + + expect(verifyMerkleClaim(tree, claim, proofs)).toBe(true); + }); + + it('should return false if the claim is invalid', () => { + const { tree } = generateMerkleTree(claimsInput); + const claim: ProvableClaim = { + address: '0x36446eF671954753801f9d73C415a80C0e550b32', + amount: 200 + }; + const proof = getMerkleProofs(tree, claim); + expect(verifyMerkleClaim(tree, claim, proof)).toBe(false); + }); + + it('should sort inputs so that it is not reliant on ordering of the claims', () => { + function shuffleArray(array: T[]): T[] { + const newArray = [...array]; // Create a copy of the array to avoid mutating the original + for (let i = newArray.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)); + [newArray[i], newArray[j]] = [newArray[j], newArray[i]]; // Swap elements + } + return newArray; + } + + const shuffledOne = shuffleArray(claimsInput); + + const shuffledTwo = shuffleArray(claimsInput); + + // Make sure sorting worked + expect(JSON.stringify(shuffledOne)).not.toEqual(JSON.stringify(shuffledTwo)); + + const { rootHash: rootHashOne } = generateMerkleTree(shuffledOne); + const { rootHash: rootHashTwo } = generateMerkleTree(shuffledTwo); + + expect(rootHashOne).toEqual(rootHashTwo); + }); +}); diff --git a/src/lib/protocol/proofs/merkleTree.ts b/src/lib/protocol/proofs/merkleTree.ts new file mode 100644 index 00000000..d367fe26 --- /dev/null +++ b/src/lib/protocol/proofs/merkleTree.ts @@ -0,0 +1,37 @@ +import { MerkleTree } from 'merkletreejs'; +import type { Address } from 'viem'; +import { keccak256, encodePacked } from 'viem/utils'; + +export type ProvableClaim = { + address: Address; + amount: number; +}; + +function hashLeaf(claim: ProvableClaim): string { + // Mimic Solidity's keccak256(abi.encodePacked(address, amount)) + const packedData = encodePacked(['address', 'uint256'], [claim.address, BigInt(claim.amount)]); + return keccak256(packedData); +} + +export function generateMerkleTree(claims: ProvableClaim[]): { tree: MerkleTree; rootHash: string } { + const inputs = claims.map(hashLeaf); + + const tree = new MerkleTree(inputs, keccak256, { + sort: true + // concatenator: ([left, right]) => Buffer.from(hashLeaf(Buffer.concat([left, right]).toString())) + }); + + return { + tree, + rootHash: tree.getRoot().toString('hex') + }; +} + +export function getMerkleProofs(tree: MerkleTree, claim: ProvableClaim): `0x${string}`[] { + return tree.getHexProof(hashLeaf(claim)) as `0x${string}`[]; +} + +export function verifyMerkleClaim(tree: MerkleTree, claim: ProvableClaim, proof: string[]): boolean { + const root = tree.getRoot().toString('hex'); + return tree.verify(proof, hashLeaf(claim), root); +} diff --git a/src/prisma/migrations/20241107150504_onchain_claims/migration.sql b/src/prisma/migrations/20241107150504_onchain_claims/migration.sql new file mode 100644 index 00000000..60e64977 --- /dev/null +++ b/src/prisma/migrations/20241107150504_onchain_claims/migration.sql @@ -0,0 +1,68 @@ +-- AlterTable +ALTER TABLE "BuilderEvent" ADD COLUMN "weeklyClaimId" UUID; + +-- AlterTable +ALTER TABLE "GemsReceipt" ADD COLUMN "onchainAttestationRevoked" BOOLEAN DEFAULT false, +ADD COLUMN "onchainAttestationUid" TEXT, +ADD COLUMN "onchainChainId" INTEGER; + +-- AlterTable +ALTER TABLE "Scout" ADD COLUMN "onchainProfileAttestationChainId" INTEGER, +ADD COLUMN "onchainProfileAttestationUid" TEXT; + +-- AlterTable +ALTER TABLE "ScoutGameActivity" ADD COLUMN "tokensReceiptId" UUID; + +-- CreateTable +CREATE TABLE "WeeklyClaims" ( + "id" UUID NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "season" TEXT NOT NULL, + "week" TEXT NOT NULL, + "merkleTreeRoot" TEXT NOT NULL, + "totalClaimable" INTEGER NOT NULL, + "claims" JSONB NOT NULL, + "proofsMap" JSONB NOT NULL, + + CONSTRAINT "WeeklyClaims_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "TokensReceipt" ( + "id" UUID NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "value" INTEGER NOT NULL, + "claimedAt" TIMESTAMP(3), + "eventId" UUID NOT NULL, + "recipientId" UUID, + "senderId" UUID, + + CONSTRAINT "TokensReceipt_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "WeeklyClaims_week_key" ON "WeeklyClaims"("week"); + +-- CreateIndex +CREATE INDEX "TokensReceipt_recipientId_idx" ON "TokensReceipt"("recipientId"); + +-- CreateIndex +CREATE INDEX "TokensReceipt_senderId_idx" ON "TokensReceipt"("senderId"); + +-- CreateIndex +CREATE INDEX "TokensReceipt_eventId_idx" ON "TokensReceipt"("eventId"); + +-- AddForeignKey +ALTER TABLE "BuilderEvent" ADD CONSTRAINT "BuilderEvent_weeklyClaimId_fkey" FOREIGN KEY ("weeklyClaimId") REFERENCES "WeeklyClaims"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "ScoutGameActivity" ADD CONSTRAINT "ScoutGameActivity_tokensReceiptId_fkey" FOREIGN KEY ("tokensReceiptId") REFERENCES "TokensReceipt"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TokensReceipt" ADD CONSTRAINT "TokensReceipt_eventId_fkey" FOREIGN KEY ("eventId") REFERENCES "BuilderEvent"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TokensReceipt" ADD CONSTRAINT "TokensReceipt_recipientId_fkey" FOREIGN KEY ("recipientId") REFERENCES "Scout"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "TokensReceipt" ADD CONSTRAINT "TokensReceipt_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "Scout"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/src/prisma/schema.prisma b/src/prisma/schema.prisma index 920a5d61..0efcf862 100644 --- a/src/prisma/schema.prisma +++ b/src/prisma/schema.prisma @@ -2788,37 +2788,41 @@ enum BuilderStatus { } model Scout { - createdAt DateTime @default(now()) - id String @id @default(uuid()) @db.Uuid - email String? - path String @unique - displayName String - farcasterId Int? @unique - farcasterName String? // every user only has one farcaster account + createdAt DateTime @default(now()) + id String @id @default(uuid()) @db.Uuid + email String? + path String @unique + displayName String + farcasterId Int? @unique + farcasterName String? // every user only has one farcaster account telegramId BigInt? @unique - walletENS String? - avatar String? - bio String? - builderStatus BuilderStatus? - sendMarketing Boolean @default(false) - agreedToTermsAt DateTime? - onboardedAt DateTime? - currentBalance Int @default(0) - scoutWallet ScoutWallet[] - strikes BuilderStrike[] - events BuilderEvent[] - githubUser GithubUser[] - gemsPayoutEvents GemsPayoutEvent[] - pointsReceived PointsReceipt[] @relation("pointsReceiptRecipient") - pointsSent PointsReceipt[] @relation("pointsReceiptSender") - userWeeklyStats UserWeeklyStats[] - userSeasonStats UserSeasonStats[] - userAllTimeStats UserAllTimeStats[] - nftPurchaseEvents NFTPurchaseEvent[] @relation("nftPurchaseEventScout") - builderNfts BuilderNft[] - activities ScoutGameActivity[] - PendingNftTransaction PendingNftTransaction[] - builderCardActivities BuilderCardActivity[] + walletENS String? + avatar String? + bio String? + builderStatus BuilderStatus? + sendMarketing Boolean @default(false) + agreedToTermsAt DateTime? + onboardedAt DateTime? + currentBalance Int @default(0) + scoutWallet ScoutWallet[] + strikes BuilderStrike[] + events BuilderEvent[] + githubUser GithubUser[] + gemsPayoutEvents GemsPayoutEvent[] + pointsReceived PointsReceipt[] @relation("pointsReceiptRecipient") + pointsSent PointsReceipt[] @relation("pointsReceiptSender") + userWeeklyStats UserWeeklyStats[] + userSeasonStats UserSeasonStats[] + userAllTimeStats UserAllTimeStats[] + nftPurchaseEvents NFTPurchaseEvent[] @relation("nftPurchaseEventScout") + builderNfts BuilderNft[] + activities ScoutGameActivity[] + PendingNftTransaction PendingNftTransaction[] + builderCardActivities BuilderCardActivity[] + tokensReceived TokensReceipt[] @relation("tokenReceiptRecipient") + tokensSent TokensReceipt[] @relation("tokenReceiptSender") + onchainProfileAttestationUid String? + onchainProfileAttestationChainId Int? scoutSocialQuests ScoutSocialQuest[] dailyClaimEvents ScoutDailyClaimEvent[] dailyClaimStreakEvents ScoutDailyClaimStreakEvent[] @@ -2855,29 +2859,32 @@ enum BuilderEventType { } model BuilderEvent { - id String @id @default(uuid()) @db.Uuid - builderId String @db.Uuid - builder Scout @relation(fields: [builderId], references: [id], onDelete: Cascade) - type BuilderEventType - week String - season String - createdAt DateTime @default(now()) - description String? // for misc_event type - bonusPartner String? + id String @id @default(uuid()) @db.Uuid + builderId String @db.Uuid + builder Scout @relation(fields: [builderId], references: [id], onDelete: Cascade) + type BuilderEventType + week String + season String + createdAt DateTime @default(now()) + description String? // for misc_event type + bonusPartner String? + githubEventId String? @unique @db.Uuid + githubEvent GithubEvent? @relation(fields: [githubEventId], references: [id], onDelete: SetNull) + gemsPayoutEventId String? @unique @db.Uuid + gemsPayoutEvent GemsPayoutEvent? @relation(fields: [gemsPayoutEventId], references: [id], onDelete: SetNull) + nftPurchaseEventId String? @unique @db.Uuid + nftPurchaseEvent NFTPurchaseEvent? @relation(fields: [nftPurchaseEventId], references: [id], onDelete: SetNull) + pointsReceipts PointsReceipt[] + gemsReceipt GemsReceipt? + weeklyClaimId String? @db.Uuid + weeklyClaim WeeklyClaims? @relation(fields: [weeklyClaimId], references: [id], onDelete: SetNull) + tokensReceipts TokensReceipt[] + scoutSocialQuestId String? @unique @db.Uuid + scoutSocialQuest ScoutSocialQuest? @relation(fields: [scoutSocialQuestId], references: [id], onDelete: SetNull) dailyClaimEventId String? @unique @db.Uuid dailyClaimEvent ScoutDailyClaimEvent? @relation(fields: [dailyClaimEventId], references: [id], onDelete: SetNull) dailyClaimStreakEventId String? @unique @db.Uuid dailyClaimStreakEvent ScoutDailyClaimStreakEvent? @relation(fields: [dailyClaimStreakEventId], references: [id], onDelete: SetNull) - githubEventId String? @unique @db.Uuid - githubEvent GithubEvent? @relation(fields: [githubEventId], references: [id], onDelete: SetNull) - gemsPayoutEventId String? @unique @db.Uuid - gemsPayoutEvent GemsPayoutEvent? @relation(fields: [gemsPayoutEventId], references: [id], onDelete: SetNull) - nftPurchaseEventId String? @unique @db.Uuid - nftPurchaseEvent NFTPurchaseEvent? @relation(fields: [nftPurchaseEventId], references: [id], onDelete: SetNull) - pointsReceipts PointsReceipt[] - gemsReceipt GemsReceipt? - scoutSocialQuestId String? @unique @db.Uuid - scoutSocialQuest ScoutSocialQuest? @relation(fields: [scoutSocialQuestId], references: [id], onDelete: SetNull) @@index([builderId]) @@index([githubEventId]) @@ -3009,13 +3016,16 @@ enum GemsReceiptType { } model GemsReceipt { - id String @id @default(uuid()) @db.Uuid - eventId String @unique @db.Uuid - event BuilderEvent @relation(fields: [eventId], references: [id], onDelete: Cascade) - createdAt DateTime @default(now()) - value Int - type GemsReceiptType - activities ScoutGameActivity[] + id String @id @default(uuid()) @db.Uuid + eventId String @unique @db.Uuid + event BuilderEvent @relation(fields: [eventId], references: [id], onDelete: Cascade) + createdAt DateTime @default(now()) + value Int + type GemsReceiptType + activities ScoutGameActivity[] + onchainAttestationUid String? + onchainChainId Int? + onchainAttestationRevoked Boolean? @default(false) @@index([eventId]) } @@ -3113,6 +3123,8 @@ model ScoutGameActivity { builderStrikeId String? @db.Uuid builderStrike BuilderStrike? @relation(fields: [builderStrikeId], references: [id], onDelete: Cascade) + tokensReceipt TokensReceipt? @relation(fields: [tokensReceiptId], references: [id]) + tokensReceiptId String? @db.Uuid @@unique([userId, recipientType, pointsReceiptId]) @@unique([userId, recipientType, nftPurchaseEventId]) @@ -3193,6 +3205,35 @@ model ScoutWallet { @@index([scoutId]) } +model WeeklyClaims { + id String @id @default(uuid()) @db.Uuid + createdAt DateTime @default(now()) + season String + week String @unique + merkleTreeRoot String + totalClaimable Int + claims Json + proofsMap Json + builderEvents BuilderEvent[] +} + +model TokensReceipt { + id String @id @default(uuid()) @db.Uuid + createdAt DateTime @default(now()) + value Int + claimedAt DateTime? + eventId String @db.Uuid + event BuilderEvent @relation(fields: [eventId], references: [id], onDelete: Cascade) + recipientId String? @db.Uuid + recipient Scout? @relation(fields: [recipientId], references: [id], onDelete: Cascade, name: "tokenReceiptRecipient") + senderId String? @db.Uuid + sender Scout? @relation(fields: [senderId], references: [id], onDelete: Cascade, name: "tokenReceiptSender") + activities ScoutGameActivity[] + + @@index([recipientId]) + @@index([senderId]) + @@index([eventId]) +} model ScoutSocialQuest { id String @id @default(uuid()) @db.Uuid type String diff --git a/src/protocol.ts b/src/protocol.ts new file mode 100644 index 00000000..d2bc2abd --- /dev/null +++ b/src/protocol.ts @@ -0,0 +1,2 @@ +export * from './lib/protocol/proofs/merkleTree'; +export * from './lib/protocol/easSchemas/index';