-
-
Save ZaK3939/b2886c4c149e12f01f8a1fad5088b55a to your computer and use it in GitHub Desktop.
import { secretId } from "@env"; | |
import { successResponse } from "@object-helpers/apiResponse"; | |
import { createCoupon, generateHashBuffer, serializeCoupon } from "@object-helpers/coupons"; | |
import { getSigKeyfromKMS } from "@utility/getSigKeyfromKMS"; | |
import { simpleRateLimit } from "@utility/rateLimiter"; | |
import { getSecret } from "@utility/secret"; | |
import { APIGatewayProxyEvent, APIGatewayProxyResult } from "aws-lambda"; | |
import axios from "axios"; | |
import dayjs from "dayjs"; | |
import utc from "dayjs/plugin/utc"; | |
dayjs.extend(utc); | |
const getMultipleLogic = (count: number): number[] => { | |
let logicArr: number[] = []; | |
if (count >= 10) { | |
logicArr = [1, 2, 3]; | |
} else if (count >= 5) { | |
logicArr = [1, 2]; | |
} else if (count >= 1) { | |
logicArr = [1]; | |
} | |
return logicArr; | |
}; | |
const getRemainingTime = (): string => { | |
const now = dayjs(); | |
const tomorrow = now.add(1, "day").startOf("day"); | |
const diffInMilliseconds = tomorrow.diff(now); | |
const hours = Math.floor(diffInMilliseconds / 1000 / 60 / 60); | |
const minutes = Math.floor((diffInMilliseconds / 1000 / 60) % 60); | |
const seconds = Math.floor((diffInMilliseconds / 1000) % 60); | |
return `${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}:${seconds | |
.toString() | |
.padStart(2, "0")}`; | |
}; | |
export async function countTransactions(address: string, apiKey: string): Promise<number> { | |
try { | |
const getURL = `https://api.polygonscan.com/api?module=account&action=txlist&address=${address}&startblock=0&endblock=99999999&sort=desc&apikey=${apiKey}`; | |
console.log("getURL:", getURL); | |
const response = await axios.get(getURL); | |
if (response.data.message === "NOTOK") { | |
console.error("Error retrieving transactions:", response.data.message); | |
return 0; | |
} | |
const targetDate = dayjs.utc().format("YYYY-MM-DD"); | |
const transactions = response.data.result; | |
const startOfDay = dayjs.utc(targetDate).startOf("day"); | |
const endOfDay = dayjs.utc(targetDate).endOf("day"); | |
let count = 0; | |
for (const tx of transactions) { | |
const txDate = dayjs.unix(tx.timeStamp); | |
if (txDate.isBefore(startOfDay)) { | |
break; | |
} | |
if (txDate.isAfter(startOfDay) && txDate.isBefore(endOfDay) && tx.isError === "0" && tx.from === address) { | |
count++; | |
} | |
} | |
console.log(`Address ${address} has made ${count} successful transactions on ${targetDate}.`); | |
return count; | |
} catch (error) { | |
console.error("Error:", error); | |
return 0; | |
} | |
} | |
const RESPONSE_HEADERS = { | |
"Content-Type": "application/json", | |
"Access-Control-Allow-Origin": "*", | |
"Access-Control-Allow-Headers": "Content-Type,Authorization,access-token", | |
}; | |
export const handler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => { | |
const queryParams = event.queryStringParameters ? event.queryStringParameters : {}; | |
console.log("start dailyquest, params: " + JSON.stringify(queryParams)); | |
if (!queryParams.address) { | |
return { | |
statusCode: 500, | |
headers: RESPONSE_HEADERS, | |
body: JSON.stringify({ result: "bad request" }), | |
}; | |
} | |
let counter = 0; | |
let polygonscanApiKey; | |
try { | |
const secret = await getSecret(secretId); | |
polygonscanApiKey = secret.POLYGONSCAN_API_KEY; | |
const address = queryParams.address.toLowerCase(); | |
const allow = await simpleRateLimit(address, "transaction-verify", 60000); | |
console.log(allow); | |
if (!allow) { | |
return { | |
statusCode: 429, | |
headers: RESPONSE_HEADERS, | |
body: JSON.stringify({ result: "rate limited. come back in 1 minutes" }), | |
}; | |
} | |
counter = await countTransactions(address, polygonscanApiKey); | |
console.log("counter:", counter); | |
const coupons = []; | |
let logicIds = []; | |
if (queryParams.logicid) { | |
logicIds.push(parseInt(queryParams.logicid, 10)); | |
} else { | |
logicIds = getMultipleLogic(counter); | |
} | |
const secretkey = await getSigKeyfromKMS(secret.CiphertextBlob); | |
const todayDate = parseInt(dayjs.utc().format("YYYYMMDD"), 10); | |
for (const logicid of logicIds) { | |
const hashBuffer = generateHashBuffer(["uint32", "uint16", "address"], [todayDate, logicid, address]); | |
console.log("create buffer with", todayDate, logicid, address); | |
const signerPvtKey = Buffer.from(secretkey, "hex"); | |
const coupon = createCoupon(hashBuffer, signerPvtKey); | |
coupons.push({ | |
logic: logicid, | |
coupon: serializeCoupon(coupon), | |
}); | |
} | |
if (coupons.length > 0) { | |
const responseBody = JSON.stringify({ | |
message: "verify", | |
counter, | |
day: todayDate, | |
coupons: coupons.map(coupon => ({ logic: coupon.logic, coupon: coupon.coupon })), | |
}); | |
return successResponse(responseBody); | |
} | |
} catch (err) { | |
console.error(err); | |
} | |
return { | |
statusCode: 200, | |
headers: RESPONSE_HEADERS, | |
body: JSON.stringify({ | |
result: "not verify", | |
}), | |
}; | |
}; |
// add expire?
import { toBuffer, ecsign, bufferToHex, ECDSASignature, keccak256 } from "ethereumjs-util";
import { ethers } from "ethers/lib";
export function createCoupon(hash: Buffer, signerPvtKey: Buffer): ECDSASignature {
let res: ECDSASignature;
try {
res = ecsign(hash, signerPvtKey);
} catch (e) {
console.error(e);
throw new Error("Failed to create coupon: " + e?.toString());
}
// Attach expiry to the signature
return {
...res,
expiry: getUTCEndTime()
};
}
/**
- Generates a hash buffer.
- @param {Array} typesArray - An array of solidity types.
- @param {Array} valueArray - An array of values corresponding to the types.
- @return {Buffer} - The generated hash buffer.
*/
export function generateHashBuffer(typesArray: any, valueArray: any): Buffer {
return keccak256(toBuffer(ethers.utils.defaultAbiCoder.encode(typesArray, valueArray)));
}
/**
- Serializes a coupon object.
- @param {ECDSASignature} coupon - The coupon to serialize.
- @return {Object} - The serialized coupon object.
*/
export function serializeCoupon(coupon: ECDSASignature): Object {
return {
r: bufferToHex(coupon.r),
s: bufferToHex(coupon.s),
v: coupon.v,
expiry: coupon.expiry,
};
}
// add expiry for contract
contract PhiDaily {
struct Coupon {
bytes32 r;
bytes32 s;
uint8 v;
uint256 expiry;
}
address public adminSigner;
function _isVerifiedCoupon(bytes32 digest, Coupon memory coupon) public view returns (bool) {
require(coupon.expiry > block.timestamp, "Coupon has expired");
address signer = ecrecover(digest, coupon.v, coupon.r, coupon.s);
return (signer == adminSigner);
}
}
// calc expiry func
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
dayjs.extend(utc);
const getUTCEndTime = (): number => {
const now = dayjs().utc();
const tomorrowStart = now.add(1, "day").startOf("day");
return tomorrowStart.unix();
};
//Here are the proposed improvements for expiry
// createCoupon
import { toBuffer, ecsign, bufferToHex, ECDSASignature, keccak256 } from "ethereumjs-util";
import { ethers } from "ethers/lib";
export function createCoupon(hash: Buffer, signerPvtKey: Buffer): ECDSASignature {
let res: ECDSASignature;
try {
res = ecsign(hash, signerPvtKey);
} catch (e) {
console.error(e);
throw new Error("Failed to create coupon: " + e?.toString());
}
return res;
}
export function generateHashBuffer(typesArray: any, valueArray: any) {
return keccak256(toBuffer(ethers.utils.defaultAbiCoder.encode(typesArray, valueArray)));
}
export function serializeCoupon(coupon: ECDSASignature) {
return {
r: bufferToHex(coupon.r),
s: bufferToHex(coupon.s),
v: coupon.v,
};
}