-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(qf-funding-score): to calculate score for each project from past…
… 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
Showing
8 changed files
with
13,479 additions
and
10,360 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", | ||
// }), | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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×tamp=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; | ||
} | ||
} |
Oops, something went wrong.