Skip to content

Commit

Permalink
feat(qf-funding-score): to calculate score for each project from past…
Browse files Browse the repository at this point in the history
… donations

for thematters/developer-resource#350 & #103

1. add trust points details for each sender, in tsv format good for export/import spreadsheet for human review;
2. saved to s3 bucket `s3://matters-billboard/pre-rounds/` for automation later;
3. send qf notifications emails;
  • Loading branch information
49659410+tx0c committed Mar 6, 2024
1 parent f398621 commit 53bab3b
Show file tree
Hide file tree
Showing 8 changed files with 13,479 additions and 10,360 deletions.
34 changes: 34 additions & 0 deletions bin/qf-calculate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env -S node --trace-warnings --loader ts-node/esm

import { calculateQFScore } from "../lib/qf-calculate.js";

async function main() {
const args = process.argv.slice(2);
const amount = BigInt(+(args?.[0] || 500) * 1e6);

// let fromTime: string | undefined, toTime: string | undefined;
let [fromTime, toTime] =
args.length >= 0
? [args?.[1] || "2023-10-01", args?.[2] || "2024-12-31T23:59:59.999Z"]
: [undefined, undefined];
// const [fromTime,toTime] = [Date.parse(from), Date.parse(to)];
const [fromBlock, toBlock] =
Number.isNaN(Date.parse(fromTime!)) && Number.isNaN(Date.parse(toTime!)) // not date string
? [BigInt(fromTime!), BigInt(toTime!)]
: [undefined, undefined];
if (fromBlock != null && toBlock != null) {
[fromTime, toTime] = [undefined, undefined];
}

const res = await calculateQFScore({
fromTime, //: typeof fromBlock === "bigint" ? undefined : fromTime, // : "2023-12-31",
toTime, // : typeof toBlock === "bigint" ? undefined : toTime, // : "2024-12-31T23:59:59.999Z",
fromBlock,
toBlock,
amount,
write_gist: true,
});
console.log(new Date(), "res:", res);
}

main().catch((err) => console.error(new Date(), "ERROR:", err));
130 changes: 130 additions & 0 deletions handlers/qf-calculate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Context, APIGatewayProxyResult, APIGatewayEvent } from "aws-lambda";
import { calculateQFScore, sendQfNotifications } from "../lib/qf-calculate.js";
import { s3GetFile } from "../lib/utils/aws.js";

export const handler = async (
event: APIGatewayEvent & {
fromTime?: string;
toTime?: string;
fromBlock?: string;
toBlock?: string;
amount?: string;
appendToRounds?: boolean;
forceRun?: boolean;
},
context: Context
): Promise<APIGatewayProxyResult> => {
console.log(`Event: ${JSON.stringify(event, null, 2)}`);
console.log(`Context: ${JSON.stringify(context, null, 2)}`);

const { method, path } = ((event?.requestContext as any)?.http as any) || {};
// const forceRun = !!(event?.queryStringParameters as any).forceRun;
const queryStringParameters = (event?.queryStringParameters as any) || {};
const forceRun = !!("forceRun" in queryStringParameters);
const { accept, origin }: { accept?: string; origin?: string } =
event?.headers || {};
if (
!(
event?.forceRun ||
(path === "/qf-calculator" && accept?.startsWith("application/json")) ||
(path === "/send-notifications" && accept) ||
(path === "/get-rounds" && accept)
)
) {
return {
statusCode: 400,
// contentType: 'text/plain',
headers: { "content-type": "text/plain" },
// JSON.stringify({ error:
body: "input error, call with POST /qf-calculator with accept: application/json",
// }),
};
}

if (path === "/get-rounds") {
const { key } = queryStringParameters;
const res = await s3GetFile({
bucket: "matters-billboard",
key: key?.endsWith(".json") ? key : `pre-rounds/rounds.json`,
}); // .then((res) => console.log(new Date(), `s3 get pre-rounds:`, res));
console.log(new Date(), `s3 get pre-rounds:`, res.ContentLength, res);
if (!res.Body) {
return {
statusCode: 404,
headers: { "content-type": "text/plain" },
body: "file not found",
};
}
const body = await res.Body.transformToString();
return {
statusCode: 200,
headers: { "content-type": "application/json; charset=utf-8" },
body: body,
};
} else if (method === "POST" && path === "/send-notifications" && accept) {
// get distrib.json and send notifications;
const { key } = queryStringParameters;
const res = await s3GetFile({
bucket: "matters-billboard",
key: key?.endsWith(".json")
? key
: `pre-rounds/polygon-48178750-53561096/distrib.json`,
}); // .then((res) => console.log(new Date(), `s3 get pre-rounds:`, res));
console.log(new Date(), `s3 get pre-rounds:`, res.ContentLength, res);
if (!res.Body) {
return {
statusCode: 404,
headers: { "content-type": "text/plain" },
body: "file not found",
};
}
try {
const distribs = JSON.parse(await res.Body.transformToString());
const sent = await sendQfNotifications(distribs);
return {
statusCode: 200,
body: `sent qf notifications to ${sent?.length ?? 0} authors`,
};
} catch (err) {
return {
statusCode: 400,
body: "",
};
}
} else if (
method === "POST" &&
path === "/qf-calculator" &&
accept?.startsWith("application/json")
) {
const { fromTime, toTime, fromBlock, toBlock, amount, appendToRounds } =
event?.forceRun ? event : queryStringParameters;

const { root, gist_url } = await calculateQFScore({
fromTime,
toTime,
fromBlock: fromBlock ? BigInt(fromBlock) : undefined,
toBlock: toBlock ? BigInt(toBlock) : undefined,
amount: BigInt(+(amount || 500) * 1e6),
appendToRounds: !!appendToRounds,
});

return {
statusCode: 200,
headers: { "content-type": "application/json; charset=utf-8" },
body: JSON.stringify({
message: "done.",
root, // tree
gist_url,
}),
};
}

return {
statusCode: 400,
// contentType: 'text/plain',
headers: { "content-type": "text/plain; charset=utf-8" },
// JSON.stringify({ error:
body: "input error, call with POST /qf-calculator with accept: application/json",
// }),
};
};
77 changes: 77 additions & 0 deletions lib/bigint-math.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// https://golb.hplar.ch/2018/09/javascript-bigint.html
export class BigIntMath {
static max(...values: Array<bigint | number>) {
if (values.length === 0) {
return null;
}

if (values.length === 1) {
return values[0];
}

let max = values[0];
for (let i = 1; i < values.length; i++) {
if (values[i] > max) {
max = values[i];
}
}
return max;
}

static min(...values: Array<bigint | number>) {
if (values.length === 0) {
return null;
}

if (values.length === 1) {
return values[0];
}

let min = values[0];
for (let i = 1; i < values.length; i++) {
if (values[i] < min) {
min = values[i];
}
}
return min;
}

static sign(value: bigint | number) {
if (value > 0n) {
return 1n;
}
if (value < 0n) {
return -1n;
}
return 0n;
}

static abs(value: bigint | number) {
if (this.sign(value) === -1n) {
return -value;
}
return value;
}

// https://stackoverflow.com/questions/53683995/javascript-big-integer-square-root/58863398#58863398
static rootNth(value: bigint, k: bigint = 2n) {
if (value < 0n) {
throw "negative number is not supported";
}

let o = 0n;
let x = value;
let limit = 100;

while (x ** k !== k && x !== o && --limit) {
o = x;
x = ((k - 1n) * x + value / x ** (k - 1n)) / k;
}

return x;
}

static sqrt(value: bigint) {
return BigIntMath.rootNth(value);
}
}
48 changes: 48 additions & 0 deletions lib/polygonscan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
const PolygonScanAPIKEY = process.env.MATTERS_POLYGONSCANAPIKEY || "";
// https://api.polygonscan.com/api?module=block&action=getblocknobytime&timestamp=1704184772&closest=before&apikey={key}

export class PolygonScanAPI {
static async getBlockFromTimestamp({
timestamp,
closest,
}: {
timestamp: number; // Date | string | number;
closest: "before" | "after";
}) {
const nowTs = Math.floor(+Date.now() / 1e3);
const params = new URLSearchParams({
module: "block",
action: "getblocknobytime",
timestamp: Math.min(nowTs, timestamp).toString(),
closest: timestamp >= nowTs ? "before" : closest, // override to before if pass now or future timestamp
apikey: PolygonScanAPIKEY,
});
const u = new URL(`https://api.polygonscan.com/api?${params}`);
console.log(new Date(), `getblocknobytime with:`, { params, u });
const res = await fetch(u).then((res) => res.json());

// {"status":"1","message":"OK","result":"51846093"}
if (!(res?.status === "1" && res?.message === "OK"))
console.error(new Date(), `non-ok result:`, res);

return BigInt(res?.result);
}

static async getBlockReward({ blockno }: { blockno: bigint | number }) {
const params = new URLSearchParams({
module: "block",
action: "getblockreward",
blockno: blockno?.toString(),
apikey: PolygonScanAPIKEY,
});
const u = new URL(`https://api.polygonscan.com/api?${params}`);
console.log(new Date(), `get with:`, { params, u });
const res = await fetch(u).then((res) => res.json());

// {"status":"1","message":"OK","result":"51846093"}
if (!(res?.status === "1" && res?.message === "OK"))
console.error(new Date(), `non-ok result:`, res);

return res?.result;
}
}
Loading

0 comments on commit 53bab3b

Please sign in to comment.