Skip to content

Commit

Permalink
feat: add gas-sponsorship route with basic validations
Browse files Browse the repository at this point in the history
  • Loading branch information
cdotta committed Oct 24, 2024
1 parent 07b211f commit e15aafd
Show file tree
Hide file tree
Showing 3 changed files with 106 additions and 0 deletions.
2 changes: 2 additions & 0 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { withCors } from "./middleware/cors";
import { withErrorHandler } from "./middleware/error";
import { withSwagger } from "./middleware/swagger";
import { authRoutes } from "./routes/auth";
import { gasSponsorshipRoutes } from "./routes/gas-sponsorship";
import { harvestersRoutes } from "./routes/harvesters";
import { magicswapRoutes } from "./routes/magicswap";
import { projectsRoutes } from "./routes/projects";
Expand Down Expand Up @@ -125,6 +126,7 @@ const main = async () => {
app.register(transactionsRoutes(ctx));
app.register(harvestersRoutes(ctx));
app.register(magicswapRoutes(ctx));
app.register(gasSponsorshipRoutes(ctx));

app.get("/healthcheck", async (_, reply) => {
try {
Expand Down
74 changes: 74 additions & 0 deletions apps/api/src/routes/gas-sponsorship.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import type { FastifyPluginAsync } from "fastify";

import type { ErrorReply } from "../schema";
import {
type ValidateBody,
type ValidateParams,
type ValidateReply,
validateBodySchema,
validateReplySchema,
} from "../schema/gas-sponsorship";
import type { TdkApiContext } from "../types";

// TODO: Replace with actual sponsored partner IDs or logic to fetch them from another service like TMC
const fullySponsoredPatnerIds = new Set(["zeeverse", "smols"]);

export const gasSponsorshipRoutes =
({ db }: TdkApiContext): FastifyPluginAsync =>
async (app) => {
app.post<{
Params: ValidateParams;
Body: ValidateBody;
Reply: ValidateReply | ErrorReply;
}>(
"/gas-sponsorship/:partnerId/validate",
{
schema: {
body: validateBodySchema,
response: {
200: validateReplySchema,
},
},
},
async (req, reply) => {
const { body, params } = req;

const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);

const last24hTransactions = await db.transaction.count({
where: {
fromAddress: body.userOp.sender,
blockTimestamp: {
gte: yesterday,
},
},
});

const [isPartnerFullySponsored, hasLessThan10Transactions] = [
fullySponsoredPatnerIds.has(params.partnerId),
last24hTransactions < 10,
];

let reason = "does not meet the criteria";

switch (true) {
case isPartnerFullySponsored:
reason = "partner is fully sponsored";
break;
case hasLessThan10Transactions:
reason = "less than 10 transactions in the last 24 hours";
break;

default:
break;
}

const isAllowed = isPartnerFullySponsored || hasLessThan10Transactions;

reply.send({
isAllowed,
reason,
});
},
);
};
30 changes: 30 additions & 0 deletions apps/api/src/schema/gas-sponsorship.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { type Static, Type } from "@sinclair/typebox";

export const validateBodySchema = Type.Object({
clientId: Type.String(),
chainId: Type.Number(),
userOp: Type.Object({
sender: Type.String(),
targets: Type.Array(Type.String()),
gasLimit: Type.String(),
gasPrice: Type.String(),
data: Type.Object({
targets: Type.Array(Type.String()),
callDatas: Type.Array(Type.String()),
values: Type.Array(Type.String()),
}),
}),
});

export const validateReplySchema = Type.Object({
isAllowed: Type.Boolean(),
reason: Type.Optional(Type.String()),
});

const validateParamsSchema = Type.Object({
partnerId: Type.String(),
});

export type ValidateParams = Static<typeof validateParamsSchema>;
export type ValidateReply = Static<typeof validateReplySchema>;
export type ValidateBody = Static<typeof validateBodySchema>;

0 comments on commit e15aafd

Please sign in to comment.