Skip to content

Commit

Permalink
Merge branch 'main' of github.com:windranger-io/mantle-xyz into scr-2…
Browse files Browse the repository at this point in the history
…19-prepare-styling-for-the-wallet-modal
  • Loading branch information
abbylow committed Aug 3, 2023
2 parents 745fc84 + 8390527 commit c19e266
Show file tree
Hide file tree
Showing 93 changed files with 15,993 additions and 12,747 deletions.
6 changes: 3 additions & 3 deletions apps/bridge/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@
"@mantle/constants": "workspace:*",
"@mantle/supagraph": "workspace:*",
"@mantle/ui": "workspace:*",
"@mantle/utils": "workspace:*",
"@mantleio/contracts": "^0.2.0",
"@mantleio/core-utils": "^0.1.0",
"@mantleio/sdk": "^0.2.1",
"@tanstack/react-query": "^4.14.5",
"decimal.js": "^10.4.3",
"ethers": "^5.7.2",
"framer-motion": "^10.12.10",
"lodash": "^4.17.21",
Expand All @@ -32,7 +32,7 @@
"react-dom": "18.2.0",
"react-error-boundary": "^3.1.4",
"react-icons": "^4.8.0",
"wagmi": "^0.12.13",
"wagmi": "^1.3.9",
"zustand": "^4.0.0-rc.1"
},
"devDependencies": {
Expand All @@ -49,6 +49,6 @@
"eslint": "^8.30.0",
"postcss": "^8.4.18",
"tailwindcss": "^3.2.1",
"typescript": "4.9.4"
"typescript": "5.0.4"
}
}
242 changes: 242 additions & 0 deletions apps/bridge/src/app/graphql/sync/gas/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
/* eslint-disable no-await-in-loop, no-restricted-syntax, @typescript-eslint/no-loop-func, no-param-reassign */
import { NextResponse } from "next/server";

// Import mongodb client
import { DB, Mongo, Store, getEngine } from "@mantle/supagraph";
import { getMongodb } from "@providers/mongoClient";
import { MongoClient } from "mongodb";

// Import the current configuration
import config from "@supagraph/config";

// Supagraph components for claims
import { claim } from "@supagraph/handlers/claim";
import { L1ToL2MessageEntity } from "@supagraph/types";

// forces the route handler to be dynamic
export const dynamic = "force-dynamic";

// Switch out the engine for development to avoid the mongo requirment locally
Store.setEngine({
// name the connection
name: config.name,
// db is dependent on env
db:
// in production/production like environments we want to store mutations to mongo otherwise we can store them locally
process.env.NODE_ENV === "development" && config.dev
? // connect store to in-memory/node-persist store
DB.create({ kv: {}, name: config.name })
: // connect store to MongoDB
Mongo.create({
kv: {},
client: getMongodb(process.env.MONGODB_URI!),
name: config.name,
mutable: config.mutable,
}),
});

// Pull any drops that have been missed (we know these are missed because the first bridge should always has gasDropped: true)
const getMissedDrops = async (db: ReturnType<MongoClient["db"]>) => {
// query for missed gas drops (groupby $from taking the first deposit only then check gasDropped for that "from" on subsequent deposits)
return db.collection("l1ToL2Message").aggregate<{
_id: string;
id: string;
from: string;
gasDropped: boolean;
}>(
[
{
$sort: {
l1BlockNumber: 1,
},
},
{
$group: {
_id: "$from",
id: { $first: "$id" },
from: { $first: "$from" },
gasDropped: { $first: "$gasDropped" },
},
},
{
$lookup: {
from: "l1ToL2Message",
let: {
joinOn: "$from",
},
pipeline: [
{
$match: {
$expr: {
$eq: ["$from", "$$joinOn"],
},
gasDropped: true,
},
},
],
as: "alreadyDropped",
},
},
{
$project: {
id: 1,
from: 1,
gasDropped: 1,
count: {
$size: "$alreadyDropped",
},
},
},
{
$match: {
gasDropped: null,
count: {
$eq: 0,
},
},
},
],
{ maxTimeMS: 60000, allowDiskUse: true }
);
};

// mark as dropped
const markDrop = async (tx: { id: string }) => {
// clear chainId and block to skip ts updates
Store.setChainId(0);
Store.clearBlock();
// issue the gasDrop
const message = await Store.get<L1ToL2MessageEntity>(
"L1ToL2Message",
tx.id,
// we don't care what state it holds...
true
);
// mark that gas was dropped
message.set("gasDropped", true);
// save the changes
await message.save();

return true;
};

// Process any drops that have been missed between operations (send them over one at a time, one after the other)
const processMissedDrops = async (
missedIn: {
from: string;
id: string;
}[]
) => {
// set how many claims to process at once
const perBlock = 25;
// split the input into blocks to process n [perBlock] at a time
const missed = missedIn.reduce(
(missedOut, item, index) => {
// get index for block
const blockIndex = Math.floor(index / perBlock);
// set new block
missedOut[blockIndex] = missedOut[blockIndex] || [];
// push item to block
missedOut[blockIndex].push(item);

return missedOut;
},
[] as {
from: string;
id: string;
}[][]
);
// push true/false for all claims here
const processed: boolean[] = [];

// process in blocks to avoid fetch timeouts
for (const block of missed) {
await Promise.all(
block.map(async (tx) => {
// add a gas-drop claim for the sender
return (
claim(tx.from)
.then(async (result: any) => {
// so long as the claim hasn't errored (alredy claimed etc...)
if (
!result?.error ||
result.error?.meta?.target === "ClaimCode_code_key"
) {
// eslint-disable-next-line no-console
console.log(
`Gas-drop created for ${result?.data?.reservedFor || tx.from}`
);
// issue the gasDrop
processed.push(await markDrop(tx));
}
// mark as failed
processed.push(false);
})
// noop any errors
.catch(() => {
processed.push(false);
})
);
})
);
}

return processed;
};

// Expose the sync command on a route so that we can call it with a cron job
export async function GET() {
// open a checkpoint on the db...
const engine = await getEngine();

// select the named db
const db = engine?.db as Mongo;

// if we have a db
if (db.db) {
// create a checkpoint
engine?.stage?.checkpoint();

// pull the missed drops
const result = await getMissedDrops(await Promise.resolve(db.db));
const missed = await result.toArray();

// eslint-disable-next-line no-console
console.log("Missed", missed.length);

// process the missed claims
const claimed = (await processMissedDrops(missed)).filter((v) => v);

// write all updates to db
await engine?.stage?.commit();

// eslint-disable-next-line no-console
console.log("Claimed", claimed.length);

// we don't need to sync more often than once per block - and if we're using vercel.json crons we can only sync 1/min - 1/10mins seems reasonable for this
return NextResponse.json(
{
missed: missed.length,
claimed: claimed.length,
},
{
headers: {
// allow to be cached for revalidate seconds and allow caching in shared public cache (upto revalidate seconds)
"Cache-Control": `max-age=${config.revalidate}, public, s-maxage=${config.revalidate}`,
},
}
);
}

return NextResponse.json(
{
error: "no db",
},
{
headers: {
// allow to be cached for revalidate seconds and allow caching in shared public cache (upto revalidate seconds)
"Cache-Control": `max-age=${config.revalidate}, public, s-maxage=${config.revalidate}`,
},
}
);
}
3 changes: 3 additions & 0 deletions apps/bridge/src/app/graphql/sync/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import { handlers } from "@supagraph/handlers";
// Import revalidation timings from config
import config from "@supagraph/config";

// forces the route handler to be dynamic
export const dynamic = "force-dynamic";

// Object of providers by rpcUrl
const providerCache: { [rpcUrl: string]: providers.JsonRpcProvider } = {};

Expand Down
6 changes: 5 additions & 1 deletion apps/bridge/src/app/head.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {
ABSOLUTE_PATH,
APP_NAME,
L1_CHAIN_ID,
L2_CHAIN_ID,
META,
OG_TITLE,
TWITTER_DESC,
Expand All @@ -9,6 +11,8 @@ import {
import { Cookies } from "@mantle/ui";

export default function Head() {
const isTestnet = L1_CHAIN_ID === 5 || L2_CHAIN_ID === 5001;

return (
<>
<title>{APP_NAME}</title>
Expand All @@ -27,7 +31,7 @@ export default function Head() {
<meta name="twitter:description" content={`${TWITTER_DESC}`} />
<meta name="twitter:image" content={`${ABSOLUTE_PATH}/twitter.png`} />
<meta name="google" content="nositelinkssearchbox" />
<Cookies />
<Cookies siteId={isTestnet ? "176" : "174"} />
</>
);
}
10 changes: 3 additions & 7 deletions apps/bridge/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,12 @@ import "./styles/globals.css";

// Dummy components
import {
GTWalsheimRegular,
GTWalsheimMedium,
Header,
SlimFooter,
PageWrapper,
PageBackroundImage,
PageContainer,
GTWalsheim,
} from "@mantle/ui";
import LegalDisclaimer from "@components/LegalDisclaimer";

Expand All @@ -25,12 +24,9 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html
lang="en"
className={`${GTWalsheimRegular.variable} ${GTWalsheimMedium.variable}`}
>
<html lang="en" className={`${GTWalsheim.className}`}>
<Head />
<body>
<body className="antialiased">
{/* @ts-expect-error Server Component */}
<Providers>
<PageWrapper
Expand Down
Loading

0 comments on commit c19e266

Please sign in to comment.