diff --git a/.env.example b/.env.example index d4d3bd6..146baf0 100644 --- a/.env.example +++ b/.env.example @@ -2,8 +2,10 @@ PRIVATE_KEY=0x # The private key of the EOA which will be submitting the claim transaction NETWORK=testnet # testnet/mainnet TRANSACTIONS_URL= https://api-gateway.polygon.technology/api/v3/transactions/testnet # The transaction list endpoint of the bridge API service +TRANSACTIONS_API_KEY=64cbf956-198a-47e0-b4a1-2b3432d8f70d PROOF_URL= https://api-gateway.polygon.technology/api/v3/merkle-proof/testnet # The merkle proof endpoint of the bridge API service RPC_URL=https://zkyoto.explorer.startale.com # The rpc of your chain +PROOF_API_KEY=64cbf956-198a-47e0-b4a1-2b3432d8f70d BRIDGE_CONTRACT=0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582 # contract address of your bridge contract GAS_STATION_URL=https://gasstation-staging.polygon.technology/astar/zkyoto # Follow Readme to spin up your own gas estimation service SOURCE_NETWORKS=[0,1] # The list of soruce network ID's of the chains you want to monitor and process claim transactions from diff --git a/.github/taskdef/staging-taskdef.yaml b/.github/taskdef/staging-taskdef.yaml index 36ea6e2..d22436f 100644 --- a/.github/taskdef/staging-taskdef.yaml +++ b/.github/taskdef/staging-taskdef.yaml @@ -11,9 +11,9 @@ env_vars: - name: NETWORK value: testnet - name: TRANSACTIONS_URL - value: https://bridge-api-testnet-dev.polygon.technology/transactions + value: https://api-gateway.polygon.technology/api/v3/transactions/testnet - name: PROOF_URL - value: https://bridge-api-testnet-dev.polygon.technology/merkle-proof + value: https://api-gateway.polygon.technology/api/v3/merkle-proof/testnet - name: BRIDGE_CONTRACT value: "0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582" - name: GAS_STATION_URL @@ -29,3 +29,5 @@ secret_vars: - RPC_URL - SLACK_URL - SENTRY_DSN + - TRANSACTIONS_API_KEY + - PROOF_API_KEY diff --git a/package-lock.json b/package-lock.json index 60e884b..6670f76 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,7 @@ "axios": "^1.6.8", "dotenv": "^16.0.1", "ethers": "^6.12.0", - "long": "^5.2.0", - "node-cron": "^3.0.3" + "long": "^5.2.0" }, "devDependencies": { "@types/node-cron": "^3.0.11", @@ -8646,25 +8645,6 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, - "node_modules/node-cron": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", - "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", - "dependencies": { - "uuid": "8.3.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/node-cron/node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -18031,21 +18011,6 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" }, - "node-cron": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", - "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", - "requires": { - "uuid": "8.3.2" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - } - } - }, "node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", diff --git a/package.json b/package.json index dfcfac8..2a6f414 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,7 @@ "axios": "^1.6.8", "dotenv": "^16.0.1", "ethers": "^6.12.0", - "long": "^5.2.0", - "node-cron": "^3.0.3" + "long": "^5.2.0" }, "devDependencies": { "@types/node-cron": "^3.0.11", diff --git a/src/config/index.ts b/src/config/index.ts index dae0d87..953d157 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -6,11 +6,13 @@ export default { PRIVATE_KEY: process.env.PRIVATE_KEY, SOURCE_NETWORKS: process.env.SOURCE_NETWORKS || '[]', DESTINATION_NETWORK: process.env.DESTINATION_NETWORK, - TRANSACTIONS_URL: process.env.TRANSACTIONS_URL || 'https://bridge-api-testnet-dev.polygon.technology/transactions', - PROOF_URL: process.env.PROOF_URL || 'https://bridge-api-testnet-dev.polygon.technology/merkle-proof', - RPC_URL: process.env.RPC_URL || 'https://rpc.cardona.zkevm-rpc.com', - BRIDGE_CONTRACT: process.env.BRIDGE_CONTRACT || '0x528e26b25a34a4A5d0dbDa1d57D318153d2ED582', - GAS_STATION_URL: process.env.GAS_STATION_URL || 'https://gasstation.polygon.technology/zkevm', + TRANSACTIONS_URL: process.env.TRANSACTIONS_URL, + TRANSACTIONS_API_KEY: process.env.TRANSACTIONS_API_KEY, + PROOF_URL: process.env.PROOF_URL, + PROOF_API_KEY: process.env.PROOF_API_KEY, + RPC_URL: process.env.RPC_URL, + BRIDGE_CONTRACT: process.env.BRIDGE_CONTRACT, + GAS_STATION_URL: process.env.GAS_STATION_URL, SLACK_URL: process.env.SLACK_URL, NETWORK: process.env.NETWORK, LOGGER: { diff --git a/src/index.ts b/src/index.ts index 75eeb63..c0df74c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,6 @@ import AutoClaimService from "./services/auto-claim.js"; import { ethers } from 'ethers'; import config from "./config/index.js"; import bridgeAbi from "./abi/bridge.js"; -import { schedule } from "node-cron"; import SlackNotify from "./services/slack-notify.js"; import GasStation from "./services/gas-station.js"; import TransactionService from "./services/transaction.js"; @@ -22,37 +21,46 @@ Logger.create({ } }); +let autoClaimService: AutoClaimService; +async function run() { + while (true) { + await autoClaimService.claimTransactions(); + await new Promise(r => setTimeout(r, 120000)); + } +} + async function start() { try { const provider = new ethers.JsonRpcProvider(config.RPC_URL); const wallet = new ethers.Wallet(config.PRIVATE_KEY as string, provider); - + const contract = new ethers.Contract( - config.BRIDGE_CONTRACT, + config.BRIDGE_CONTRACT as string, bridgeAbi, wallet ); - + let slackNotify = null; if (config.SLACK_URL) { slackNotify = new SlackNotify(config.SLACK_URL) } - const autoClaimService = new AutoClaimService( - config.NETWORK as string, + autoClaimService = new AutoClaimService( + config.NETWORK as string, contract, new TransactionService( - config.PROOF_URL, - config.TRANSACTIONS_URL, + config.PROOF_URL as string, + config.TRANSACTIONS_URL as string, config.SOURCE_NETWORKS, - config.DESTINATION_NETWORK as string + config.DESTINATION_NETWORK as string, + config.TRANSACTIONS_API_KEY, + config.PROOF_API_KEY ), - new GasStation(config.GAS_STATION_URL), + new GasStation(config.GAS_STATION_URL as string,), slackNotify ); - // await autoClaimService.claimTransactions(); - schedule("*/1 * * * *", autoClaimService.claimTransactions.bind(autoClaimService)); + run(); } catch (error) { // Logger.error({ error }); } diff --git a/src/services/auto-claim.ts b/src/services/auto-claim.ts index 7ffe75e..8f1c7da 100644 --- a/src/services/auto-claim.ts +++ b/src/services/auto-claim.ts @@ -81,7 +81,18 @@ export default class AutoClaimService { { gasPrice } ) } - } catch (error) { + } catch (error: any) { + if (this.slackNotify) { + await this.slackNotify.notifyAdminForError({ + network: this.network, + claimType: transaction.dataType as string, + bridgeTxHash: transaction.transactionHash as string, + sourceNetwork: transaction.sourceNetwork, + destinationNetwork: transaction.destinationNetwork, + error: error.message ? error.message : JSON.stringify(error), + depositIndex: transaction.counter as number + }); + } Logger.error({ error }) } return tx; @@ -104,15 +115,7 @@ export default class AutoClaimService { let tx = await this.claim(transaction, proof, globalIndex, gasPrice); if (tx && this.slackNotify) { - await this.slackNotify.notifyAdmin({ - network: this.network, - claimType: transaction.dataType as string, - bridgeTxHash: transaction.transactionHash as string, - sourceNetwork: transaction.sourceNetwork, - destinationNetwork: transaction.destinationNetwork, - claimTxHash: tx.hash, - depositIndex: transaction.counter as number - }); + await this.slackNotify.notifyAdminForSuccess(tx.hash); Logger.info({ type: 'transactionCompleted', diff --git a/src/services/slack-notify.ts b/src/services/slack-notify.ts index ee211ba..deea552 100644 --- a/src/services/slack-notify.ts +++ b/src/services/slack-notify.ts @@ -3,9 +3,9 @@ import { INotifyParams } from "../types/index.js"; export default class SlackNotify { - constructor(private slackWebhookUrl: string) {} + constructor(private slackWebhookUrl: string) { } - async notifyAdmin(params: INotifyParams) { + async notifyAdminForError(params: INotifyParams) { await axios.post(this.slackWebhookUrl, { blocks: [ { @@ -47,14 +47,31 @@ export default class SlackNotify { type: "section", text: { type: "mrkdwn", - text: "*claimTxHash:* " + params.claimTxHash, + text: "*depositIndex:* " + params.depositIndex, }, }, { type: "section", text: { type: "mrkdwn", - text: "*depositIndex:* " + params.depositIndex, + text: "*Error:* " + params.error, + }, + }, + { + type: "divider" + }, + ], + }); + } + + async notifyAdminForSuccess(claimTxHash: string) { + await axios.post(this.slackWebhookUrl, { + blocks: [ + { + type: "section", + text: { + type: "mrkdwn", + text: "*transactionHash:* " + claimTxHash, }, }, { diff --git a/src/services/transaction.ts b/src/services/transaction.ts index 7d1a9b9..6464962 100644 --- a/src/services/transaction.ts +++ b/src/services/transaction.ts @@ -10,6 +10,8 @@ export default class TransactionService { private transactionUrl: string, private sourceNetworks: string, private destinationNetwork: string, + private transactionApiKey: string | undefined, + private proofApiKey: string | undefined, ) { } async getPendingTransactions(): Promise { @@ -24,8 +26,15 @@ export default class TransactionService { JSON.parse(this.sourceNetworks).forEach((networkId: number) => { sourceNetworkIds = `${sourceNetworkIds}&sourceNetworkIds=${networkId}` }) + let headers = {}; + if (this.transactionApiKey) { + headers = { + 'x-access-token': this.transactionApiKey + } + } let transactionData = await axios.get( - `${this.transactionUrl}?userAddress=${sourceNetworkIds}&destinationNetworkIds=${this.destinationNetwork}&status=READY_TO_CLAIM` + `${this.transactionUrl}?userAddress=${sourceNetworkIds}&destinationNetworkIds=${this.destinationNetwork}&status=READY_TO_CLAIM`, + { headers } ); if (transactionData && transactionData.data && transactionData.data.result) { transactions = transactionData.data.result; @@ -59,7 +68,16 @@ export default class TransactionService { }) let proof: IProof | null = null; try { - let proofData = await axios.get(`${this.proofUrl}?networkId=${sourceNetwork}&depositCount=${depositCount}`); + let headers = {}; + if (this.proofApiKey) { + headers = { + 'x-access-token': this.proofApiKey + } + } + let proofData = await axios.get( + `${this.proofUrl}?networkId=${sourceNetwork}&depositCount=${depositCount}`, + { headers } + ); if ( proofData && proofData.data && proofData.data.proof && proofData.data.proof.merkle_proof && !proofData.data.proof.merkle_proof.message diff --git a/src/types/index.ts b/src/types/index.ts index 271e7cf..c05eef6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -4,7 +4,7 @@ export interface INotifyParams { bridgeTxHash: string sourceNetwork: number destinationNetwork: number - claimTxHash: string + error: string depositIndex: number }