Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/mongo-container.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ wait_for_mongodb() {
manage_collections() {
echo "Creating/Clearing collections..."
docker exec mongodb mongosh --eval '
db = db.getSiblingDB("myDatabase");
db = db.getSiblingDB("points");
db.referrals.drop();
db.createCollection("referrals");
print("Collections have been reset successfully.");
Expand Down
22 changes: 22 additions & 0 deletions backend/requests.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
### Query Code
GET http://localhost:3000/api/leaderboard/get-code/AMw74tWw61gxbvVrYTrKrPD5ZKtF5hgRt4hovBFumEtt


### Get all codes
GET http://localhost:3000/api/leaderboard/get-referral-codes


### Insert mock codes
POST http://localhost:3000/api/leaderboard/insert-mock-codes



### Use code
POST http://localhost:3000/api/leaderboard/use-code
Content-Type: application/json

{
"address": "AMw74tWw61gxbvVrYTrKrPD5ZKtF5hgRt4hovBFumEtt",
"code": "rtowulqn",
"signature": []
}
121 changes: 117 additions & 4 deletions backend/src/controllers/referral.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,27 @@ export const getReferralCodes = async (
res: FastifyReply
) => {
const collection = new Collection(Collections.Referrals);
const referrals = await collection.getAllElementsAsArray();
res.send(referrals);
const referrals = await collection.getReferrersAndReferred();
const filteredReferrals = referrals.reduce(
(
acc: Record<
string,
{
signature: string | null;
code: string;
codeUsed: string | null;
invited: string[];
}
>,
curr
) => {
const { address, _id, ...data } = curr;
acc[address] = data as Omit<IReferralCollectionItem, "address">;
return acc;
},
{}
);
res.send(filteredReferrals);
};

export const useCode = async (
Expand All @@ -51,7 +70,14 @@ export const useCode = async (
session.startTransaction();
const referrerEntry = await collection.findOne({ code });
const userEntry = await collection.findOne({ address });
if (!referrerEntry || (userEntry && !!userEntry.codeUsed)) {

if (!referrerEntry || (userEntry && userEntry.codeUsed)) {
await session.abortTransaction();
res.status(400).send({ ok: false });
return;
}

if (userEntry?.address === referrerEntry.address) {
await session.abortTransaction();
res.status(400).send({ ok: false });
return;
Expand Down Expand Up @@ -82,7 +108,7 @@ export const getCode = async (
const { address } = req.params;
const userEntry = await collection.findOne({ address });
if (userEntry) {
return res.status(200).send({ code: userEntry.codeOwned });
return res.status(200).send({ code: userEntry.code });
}
const codeOwned = getRandomCode();
const newUserEntry: IReferralCollectionItem = {
Expand All @@ -95,3 +121,90 @@ export const getCode = async (
await collection.insertOne(newUserEntry);
return res.status(200).send({ code: codeOwned });
};

export const insertMockCodes = async (
req: FastifyRequest,
res: FastifyReply
) => {
const collection = new Collection(Collections.Referrals);
const addresses = [
"BtGH2WkM1oNyPVgzYT51xV2gJqHhVQ4QiGwWirBUW5xN", //
"A3sjWSa4rNspqqZQFjMMbmmnbgvYDRRMiWjyaTbMyYG4", // uses first address code
"9BNu7C9f3cGS4fBS5jKvG2XASHGVVnBxR2XAW4q4ZYUE", // uses first address code
"63CQPyL9xzCSy4ejNuqxxqfjRpAGVKa1KbJUvXfRSKZK", // uses third address code
"6usfBWrWdKoA5BvyAqjjU4R8yKuWbP8JkofrtPk3Rupw", // uses third address code
];

const dbClient = app.mongoClient;

const firstAddress = addresses[0];
const firstAddressEntry: IReferralCollectionItem = {
address: firstAddress,
code: getRandomCode(),
codeUsed: null,
signature: null,
invited: [],
};
await collection.insertOne(firstAddressEntry);

for (const address of [addresses[1], addresses[2]]) {
const session = dbClient.startSession();
try {
session.startTransaction();

const referrerEntry = await collection.findOne({ address: firstAddress });
const userEntry = await collection.findOne({ address });

if (!referrerEntry || (userEntry && !!userEntry.codeUsed)) {
await session.abortTransaction();
continue;
}

await collection.addToInvitedList(referrerEntry.code, address);
await collection.insertOrUpdateOne(
address,
getRandomCode(),
referrerEntry.code,
"signature"
);

await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
} finally {
await session.endSession();
}
}

const thirdAddress = addresses[2];
for (const address of [addresses[3], addresses[4]]) {
const session = dbClient.startSession();
try {
session.startTransaction();

const referrerEntry = await collection.findOne({ address: thirdAddress });
const userEntry = await collection.findOne({ address });

if (!referrerEntry || (userEntry && !!userEntry.codeUsed)) {
await session.abortTransaction();
continue;
}

await collection.addToInvitedList(referrerEntry.code, address);
await collection.insertOrUpdateOne(
address,
getRandomCode(),
referrerEntry.code,
"signature"
);

await session.commitTransaction();
} catch (err) {
await session.abortTransaction();
} finally {
await session.endSession();
}
}

res.status(200).send({ ok: true });
};
2 changes: 2 additions & 0 deletions backend/src/routes/referral.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
getCode,
useCode,
getReferralCodes,
insertMockCodes,
} from "../controllers/referral.controller";
import { FastifyInstance, FastifyPluginAsync } from "fastify";
import fp from "fastify-plugin";
Expand All @@ -12,6 +13,7 @@ const leaderboardRoutes: FastifyPluginAsync = async (
fastify.get("/api/leaderboard/get-code/:address", getCode);
fastify.post("/api/leaderboard/use-code", useCode);
fastify.get("/api/leaderboard/get-referral-codes", getReferralCodes);
fastify.post("/api/leaderboard/insert-mock-codes", insertMockCodes);
};

export default fp(leaderboardRoutes);
11 changes: 11 additions & 0 deletions backend/src/services/collection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,17 @@ export class Collection {
);
}

async getReferrersAndReferred() {
return await this.db
.find({
$or: [
{ invited: { $exists: true, $not: { $size: 0 } } },
{ codeUsed: { $exists: true, $ne: null } },
],
})
.toArray();
}

async insertOne(element: any) {
return await this.db.insertOne(element);
}
Expand Down
17 changes: 17 additions & 0 deletions backend/src/test/use-code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,23 @@ describe("Use code endpoint", () => {
expect(body.code).toBe(lastElement.code);
expect(allRecordsBefore.length).toBe(allRecordsAfter.length - 1);
expect(statusCode).toBe(200);

{
const payload = getMessagePayload(address.publicKey, code);
const signature = signMessage(address, decodeUTF8(payload));
const response = await fastify.inject({
method: "POST",
url: "/api/leaderboard/use-code",
payload: {
address: address.publicKey.toString(),
code,
signature: Buffer.from(signature).toString("base64"),
},
});

const statusCode = response.statusCode;
expect(statusCode).toBe(400);
}
});
test("Use code", async () => {
const address = Keypair.generate();
Expand Down
3 changes: 3 additions & 0 deletions src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,6 @@ export const FULL_SNAP_START_TX_HASH_MAINNET =
"zt4f4PYU2qKyvevjvED2Q9RSUJbiGSJns8NCQGAuLFgrTJ8irentnaEzc7uxxoi65vtmWxhwZh8HDg6NRsWjQxw";
export const FULL_SNAP_START_TX_HASH_TESTNET =
"AmjrAbNvGU8qK6xFTGpPCFPcYruZvH7gZ46YtxFyMp58x9UK3MXJ3CC3UojBvptxiAjip7fU4txZtQMoJ6Sc6kf";

export const USE_CODE_PERCENTAGE = new BN(50000); // 5%
export const REFERRER_CUT = new BN(100000); // 10%
4 changes: 4 additions & 0 deletions src/math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,17 @@ import { BN } from "@coral-xyz/anchor";
const MAX_U128 = new BN("340282366920938463463374607431768211455");
const SECONDS_PER_LIQUIDITY_DECIMAL = 24;
const LIQUIDITY_DECIMAL = 6;
export const PERCENTAGE_DECIMAL = 6;
export const POINTS_DECIMAL = 8;
const SECONDS_INSIDE_DECIMAL = 2;
const LIQUIDITY_DENOMINATOR = new BN(10).pow(new BN(LIQUIDITY_DECIMAL));
const SECONDS_PER_LIQUIDITY_DENOMINATOR = new BN(10).pow(
new BN(SECONDS_PER_LIQUIDITY_DECIMAL)
);
export const POINTS_DENOMINATOR = new BN(10).pow(new BN(POINTS_DECIMAL));
export const PERCENTAGE_DENOMINATOR = new BN(10).pow(
new BN(PERCENTAGE_DECIMAL)
);
const SECONDS_INSIDE_DENOMINATOR = new BN(10).pow(
new BN(SECONDS_INSIDE_DECIMAL)
);
Expand Down
38 changes: 37 additions & 1 deletion src/prepare-data.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { Network } from "@invariant-labs/sdk-eclipse";
import fs from "fs";
import path from "path";
import { IPointsHistoryJson, IPointsJson } from "./types";
import { IPointsHistoryJson, IPointsJson, IReferral } from "./types";
// import ECLIPSE_TESTNET_POINTS from "../data/points_testnet.json";
import ECLIPSE_MAINNET_POINTS from "../data/points_mainnet.json";
import { BN } from "@coral-xyz/anchor";
import { getReferralCodes } from "./utils";
import { REFERRER_CUT, USE_CODE_PERCENTAGE } from "./consts";
import { PERCENTAGE_DENOMINATOR } from "./math";

// eslint-disable-next-line @typescript-eslint/no-var-requires
require("dotenv").config();
Expand Down Expand Up @@ -38,6 +41,39 @@ export const prepareFinalData = async (network: Network) => {
);
});

const referrals: Record<string, IReferral> = await getReferralCodes();
const bonusPoints = {};

for (const [address, referral] of Object.entries(referrals)) {
let useCodeBonus = new BN(0);
let referrersBonus = new BN(0);

if (referral.codeUsed) {
// 5% boost for self generated points
useCodeBonus = new BN(data[address].totalPoints, "hex")
.mul(USE_CODE_PERCENTAGE)
.div(PERCENTAGE_DENOMINATOR);
}

if (referral.invited.length !== 0) {
for (const referred of referral.invited) {
if (data[referred]) {
// 10% of referred points
referrersBonus = referrersBonus.add(
new BN(data[referred].totalPoints, "hex")
.mul(REFERRER_CUT)
.div(PERCENTAGE_DENOMINATOR)
);
}
}
}

bonusPoints[address] = {
useCodeBonus,
referrersBonus,
};
}

const finalData = Object.keys(data)
.map((key) => {
return {
Expand Down
7 changes: 7 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,10 @@ export interface IPromotedPool {
pointsPerSecond: BN;
startCountTimestamp: BN;
}

export interface IReferral {
code: string;
codeUsed: string | null;
signature: string;
invited: string[];
}
18 changes: 17 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,13 @@ import {
RETRY_DELAY,
} from "./consts";
import { BN } from "@coral-xyz/anchor";
import { IActive, IClosed, IPoolAndTicks, IPromotedPool } from "./types";
import {
IActive,
IClosed,
IPoolAndTicks,
IPromotedPool,
IReferral,
} from "./types";
import {
calculatePointsToDistribute,
calculateReward,
Expand Down Expand Up @@ -362,3 +368,13 @@ export const fetchPoolsWithTicks = async (

return fetchPoolsWithTicks(retries + 1, market, connection, promotedPools);
};

export const getReferralCodes = async (): Promise<
Record<string, IReferral>
> => {
const response = await fetch(
"http://localhost:3000/api/leaderboard/get-referral-codes"
);
const data = await response.json();
return data as Record<string, IReferral>;
};