Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ CLAIM_CONTRACT_ADDRESS=0x
ROLLUP_CONTRACT_ADDRESS=0x
MINTER_CONTRACT_ADDRESS=0x
MINTER_CONTRACT_DEPLOYED_BLOCK=0
MAINNET_BRIDGE_O_APP_CONTRACT_ADDRESS=0x
MAINNET_BRIDGE_O_APP_CONTRACT_DEPLOYED_BLOCK=0
BASE_BRIDGE_O_APP_CONTRACT_ADDRESS=0x
BASE_BRIDGE_O_APP_CONTRACT_DEPLOYED_BLOCK=0

# messenger contract
L1_SCROLL_MESSENGER_CONTRACT_ADDRESS=0x
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ The project is divided into the following workspaces:
```sh
packages
├── block-sync-monitor
├── bridge-event-watcher
├── bridge-monitor
├── deposit-analyzer
├── indexer
├── indexer-cache-validator
Expand Down Expand Up @@ -74,7 +76,6 @@ gcloud emulators firestore start

# Set the FIRESTORE_EMULATOR_HOST variable in the same terminal where you will run your application.
export FIRESTORE_EMULATOR_HOST="HOST:PORT"
export FIRESTORE_EMULATOR_HOST="HOST:PORT" # We will use what is displayed in the console.
```

## Docker
Expand Down
17 changes: 17 additions & 0 deletions packages/bridge-event-watcher/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# bridge-event-watcher

bridge-event-watcher is a tool designed to monitor and process bridge transactions on blockchain networks.

## Usage

To set up the development environment:

```bash
# install
yarn

# dev
yarn workspace bridge-event-watcher dev

# build
yarn build
19 changes: 19 additions & 0 deletions packages/bridge-event-watcher/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "bridge-event-watcher",
"version": "1.0.0",
"dependencies": {
"@intmax2-function/shared": "workspace:*"
},
"scripts": {
"start": "node dist/index.js",
"dev": "tsx watch --env-file=./../../.env src/index.ts",
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest"
},
"devDependencies": {
"tsx": "^4.20.4",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
}
}
14 changes: 14 additions & 0 deletions packages/bridge-event-watcher/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export enum MessageStatus {
INFLIGHT = "INFLIGHT",
CONFIRMING = "CONFIRMING",
VERIFIED = "VERIFIED",
DELIVERED = "DELIVERED",
FAILED = "FAILED",
PAYLOAD_STORED = "PAYLOAD_STORED",
BLOCKED = "BLOCKED",
}

export const LAYER_ZERO_SCAN_API = {
["l1"]: "https://scan.layerzero-api.com/v1",
["l2"]: "https://scan-testnet.layerzero-api.com/v1",
} as const;
27 changes: 27 additions & 0 deletions packages/bridge-event-watcher/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { logger, timeOperation } from "@intmax2-function/shared";
import { name } from "../package.json";
import { performJob } from "./service/job.service";

async function main() {
try {
logger.info(`Starting ${name} job`);
const { durationInSeconds } = await timeOperation(performJob);
logger.info(`Completed ${name} job executed successfully in ${durationInSeconds}s`);
process.exit(0);
} catch (error) {
logger.error(error);
process.exit(1);
}
}

process.on("unhandledRejection", (reason, promise) => {
logger.error(`Unhandled Rejection at: ${promise} reason: ${reason}`);
process.exit(1);
});

if (require.main === module) {
main().catch((error) => {
logger.error(error);
process.exit(1);
});
}
72 changes: 72 additions & 0 deletions packages/bridge-event-watcher/src/service/job.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
BASE_BRIDGE_O_APP_CONTRACT_ADDRESS,
BASE_BRIDGE_O_APP_CONTRACT_DEPLOYED_BLOCK,
BLOCK_RANGE_TINY,
type BridgeRequestedEvent,
BridgeTransaction,
bridgeRequestedEvent,
createNetworkClient,
Event,
type EventData,
FIRESTORE_DOCUMENT_EVENTS,
fetchEvents,
getStartBlockNumber,
logger,
validateBlockRange,
} from "@intmax2-function/shared";

export const performJob = async () => {
const l2Client = createNetworkClient("l2");
const event = new Event(FIRESTORE_DOCUMENT_EVENTS.BRIDGE_REQUESTED);

const [currentBlockNumber, lastProcessedEvent] = await Promise.all([
await l2Client.getBlockNumber(),
await event.getEvent<EventData>(),
]);

await processBridgeMonitor(l2Client, currentBlockNumber, event, lastProcessedEvent);
};

const processBridgeMonitor = async (
l2Client: ReturnType<typeof createNetworkClient>,
currentBlockNumber: bigint,
event: Event,
lastProcessedEvent: EventData | null,
) => {
const startBlockNumber = getStartBlockNumber(
lastProcessedEvent,
BASE_BRIDGE_O_APP_CONTRACT_DEPLOYED_BLOCK,
);
const isValid = validateBlockRange("BridgeRequested", startBlockNumber, currentBlockNumber);
if (!isValid) {
logger.info("Skipping process BridgeRequested due to invalid block range.");
return;
}

const bridgeRequestedEvents = await fetchEvents<BridgeRequestedEvent>(l2Client, {
startBlockNumber: BigInt(BASE_BRIDGE_O_APP_CONTRACT_DEPLOYED_BLOCK),
endBlockNumber: currentBlockNumber,
blockRange: BLOCK_RANGE_TINY,
contractAddress: BASE_BRIDGE_O_APP_CONTRACT_ADDRESS,
eventInterface: bridgeRequestedEvent,
});

const bridgeRequestedInputs = bridgeRequestedEvents.map((event) => ({
guid: event.args.receipt.guid,
nonce: Number(event.args.receipt.nonce),
recipient: event.args.recipient,
amount: event.args.amount.toString(),
transactionHash: event.transactionHash,
}));

await BridgeTransaction.getInstance().saveBridgeTransactionsBatch(bridgeRequestedInputs);

await updateEventState(event, currentBlockNumber);
};

const updateEventState = async (event: Event, currentBlockNumber: bigint) => {
const eventData = {
lastBlockNumber: Number(currentBlockNumber),
};
await event.addOrUpdateEvent(eventData);
};
25 changes: 25 additions & 0 deletions packages/bridge-event-watcher/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"sourceMap": true,
"isolatedModules": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"allowUnreachableCode": false,
"incremental": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts", "dist"]
}
17 changes: 17 additions & 0 deletions packages/bridge-monitor/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# bridge-monitor

The bridge-monitor is a tool designed to monitor and process bridge transactions on blockchain networks.

## Usage

To set up the development environment:

```bash
# install
yarn

# dev
yarn workspace bridge-monitor dev

# build
yarn build
21 changes: 21 additions & 0 deletions packages/bridge-monitor/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "bridge-monitor",
"version": "1.0.0",
"dependencies": {
"@intmax2-function/shared": "workspace:*",
"axios": "^1.11.0",
"viem": "^2.34.0"
},
"scripts": {
"start": "node dist/index.js",
"dev": "tsx watch --env-file=./../../.env src/index.ts",
"build": "tsc",
"typecheck": "tsc --noEmit",
"test": "vitest"
},
"devDependencies": {
"tsx": "^4.20.4",
"typescript": "^5.9.2",
"vitest": "^3.2.4"
}
}
4 changes: 4 additions & 0 deletions packages/bridge-monitor/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const LAYER_ZERO_SCAN_API = {
["mainnet"]: "https://scan.layerzero-api.com/v1",
["testnet"]: "https://scan-testnet.layerzero-api.com/v1",
} as const;
27 changes: 27 additions & 0 deletions packages/bridge-monitor/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { logger, timeOperation } from "@intmax2-function/shared";
import { name } from "../package.json";
import { performJob } from "./service/job.service";

async function main() {
try {
logger.info(`Starting ${name} job`);
const { durationInSeconds } = await timeOperation(performJob);
logger.info(`Completed ${name} job executed successfully in ${durationInSeconds}s`);
process.exit(0);
} catch (error) {
logger.error(error);
process.exit(1);
}
}

process.on("unhandledRejection", (reason, promise) => {
logger.error(`Unhandled Rejection at: ${promise} reason: ${reason}`);
process.exit(1);
});

if (require.main === module) {
main().catch((error) => {
logger.error(error);
process.exit(1);
});
}
3 changes: 3 additions & 0 deletions packages/bridge-monitor/src/lib/blockchain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { createNetworkClient } from "@intmax2-function/shared";

export const l1Client = createNetworkClient("l1");
59 changes: 59 additions & 0 deletions packages/bridge-monitor/src/service/job.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {
BridgeTransaction,
type BridgeTransactionData,
BridgeTransactionStatus,
} from "@intmax2-function/shared";
import {
fetchBridgeGuidTransaction,
handleFailedStatus,
handleInflightOrConfirming,
handlePayloadStored,
handleVerifiedStatus,
} from "./process.service";

export const performJob = async () => {
const bridgeTransactions = await BridgeTransaction.getInstance().fetchBridgeTransactions({
statuses: [
BridgeTransactionStatus.QUEUED,
BridgeTransactionStatus.INFLIGHT,
BridgeTransactionStatus.CONFIRMING,
BridgeTransactionStatus.VERIFIED,
BridgeTransactionStatus.BLOCKED,
],
});

const sortedTransactions = bridgeTransactions.sort((a, b) => a.nonce - b.nonce);

for (const bridgeTransaction of sortedTransactions) {
await processBridgeTransaction(bridgeTransaction);
}
};

const processBridgeTransaction = async (bridgeTransaction: BridgeTransactionData) => {
// TODO: sleep and retry
const bridgeGuidTransaction = await fetchBridgeGuidTransaction(bridgeTransaction.guid);
const statusName = bridgeGuidTransaction.status.name;

switch (statusName) {
case BridgeTransactionStatus.FAILED:
await handleFailedStatus(bridgeGuidTransaction);
break;

case BridgeTransactionStatus.INFLIGHT:
case BridgeTransactionStatus.CONFIRMING:
await handleInflightOrConfirming(bridgeTransaction);
break;

case BridgeTransactionStatus.VERIFIED:
await handleVerifiedStatus(bridgeTransaction);
break;

case BridgeTransactionStatus.PAYLOAD_STORED:
await handlePayloadStored(bridgeGuidTransaction);
break;
}

await BridgeTransaction.getInstance().updateBridgeTransaction(bridgeTransaction.guid, {
status: statusName as BridgeTransactionStatus,
});
};
Loading