Skip to content

Instantly share code, notes, and snippets.

@ZaK3939
Created July 7, 2023 10:51
Show Gist options
  • Save ZaK3939/b2886c4c149e12f01f8a1fad5088b55a to your computer and use it in GitHub Desktop.
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",
}),
};
};
@ZaK3939
Copy link
Author

ZaK3939 commented Jul 7, 2023

//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,
};
}

@ZaK3939
Copy link
Author

ZaK3939 commented Jul 7, 2023

// 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,
    };
    }

@ZaK3939
Copy link
Author

ZaK3939 commented Jul 7, 2023

// 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);
}

}

@ZaK3939
Copy link
Author

ZaK3939 commented Jul 7, 2023

// 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();
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment