├── .env.test ├── .github └── workflows │ └── deploy.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── README.md ├── docker └── Dockerfile ├── ecosystem.config.js ├── package-lock.json ├── package.json ├── resources └── api-gateway-errors.yml ├── serverless.yml ├── soroban_rpc.sqlite ├── sql └── data.sql ├── src ├── adapters │ ├── across │ │ └── index.ts │ ├── allbridge-classic │ │ └── index.ts │ ├── allbridge-core │ │ ├── eventParsing.ts │ │ └── index.ts │ ├── arbitrum │ │ └── index.ts │ ├── assetchain-bridge │ │ └── index.ts │ ├── avalanche-btc │ │ └── index.ts │ ├── avalanche │ │ └── index.ts │ ├── axelar-satellite │ │ ├── constant.ts │ │ ├── index.ts │ │ └── type.ts │ ├── axelar │ │ ├── index.ts │ │ └── utils.ts │ ├── base │ │ └── index.ts │ ├── beamer │ │ └── index.ts │ ├── binancepeg │ │ ├── flows.ts │ │ └── index.ts │ ├── bunnyfi │ │ └── index.ts │ ├── butternetwork │ │ └── index.ts │ ├── butterswap │ │ └── index.ts │ ├── celer │ │ └── index.ts │ ├── chainport │ │ └── index.ts │ ├── circle │ │ └── index.ts │ ├── cometbridge │ │ └── index.ts │ ├── connext │ │ └── index.ts │ ├── crowdswap │ │ └── index.ts │ ├── debridgedln │ │ └── index.ts │ ├── eclipse │ │ └── index.ts │ ├── everclear │ │ └── index.ts │ ├── eywa │ │ └── index.ts │ ├── fuel │ │ └── index.ts │ ├── fuse │ │ └── index.ts │ ├── garden │ │ └── index.ts │ ├── helixbox │ │ └── index.ts │ ├── hop │ │ ├── index.ts │ │ └── test.ts │ ├── hyperlane │ │ └── index.ts │ ├── hyperliquid │ │ └── index.ts │ ├── ibc │ │ ├── errors.ts │ │ └── index.ts │ ├── index.ts │ ├── interport-finance │ │ ├── constants.ts │ │ ├── index.ts │ │ └── processTransactionsCustom.ts │ ├── intersoon │ │ └── index.ts │ ├── layerswap │ │ └── index.ts │ ├── layerzero │ │ ├── allChains.ts │ │ └── index.ts │ ├── lighter │ │ └── index.ts │ ├── manta │ │ └── index.ts │ ├── mantle │ │ └── index.ts │ ├── memebridge │ │ └── index.ts │ ├── meson │ │ └── index.ts │ ├── mesprotocol │ │ └── index.ts │ ├── meter │ │ └── index.ts │ ├── minibridge │ │ └── index.ts │ ├── mint │ │ └── index.ts │ ├── mode │ │ └── index.ts │ ├── movement │ │ └── index.ts │ ├── multichain │ │ └── index.ts │ ├── neuron │ │ └── index.ts │ ├── oooo │ │ └── index.ts │ ├── optics │ │ └── index.ts │ ├── optimism │ │ └── index.ts │ ├── orbitbridge │ │ └── index.ts │ ├── orbiter │ │ ├── abi.json │ │ ├── index.ts │ │ └── processTransaction.ts │ ├── owlto │ │ └── index.ts │ ├── pepeteam-bridge │ │ └── index.ts │ ├── pnetwork │ │ └── index.ts │ ├── polygon │ │ └── index.ts │ ├── polygon_zkevm │ │ └── index.ts │ ├── polynetwork │ │ └── index.ts │ ├── portal │ │ ├── README.md │ │ ├── consts.ts │ │ ├── index.ts │ │ ├── sui.ts │ │ └── tests.ts │ ├── rainbowbridge │ │ └── index.ts │ ├── retrobridge │ │ └── index.ts │ ├── rhinofi │ │ └── index.ts │ ├── rootstock-fastbtc-bridge │ │ └── index.ts │ ├── rootstock-flyover │ │ └── index.ts │ ├── rootstock-token-bridge │ │ ├── constant.ts │ │ └── index.ts │ ├── rootstock │ │ └── index.ts │ ├── router │ │ ├── assetForwarder.json │ │ └── index.ts │ ├── shimmerbridge │ │ └── index.ts │ ├── squid │ │ ├── constants.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── stargate │ │ └── index.ts │ ├── suibridge │ │ └── index.ts │ ├── symbiosis │ │ ├── constants.ts │ │ ├── contracts.ts │ │ ├── events.ts │ │ └── index.ts │ ├── synapse │ │ └── index.ts │ ├── test.ts │ ├── threshold-network │ │ └── index.ts │ ├── train │ │ └── index.ts │ ├── universalx │ │ └── index.ts │ ├── usdt0 │ │ └── index.ts │ ├── wanbridge │ │ └── index.ts │ ├── wormhole │ │ └── index.ts │ ├── xdai │ │ └── index.ts │ ├── xswap │ │ └── index.ts │ ├── xy-finance │ │ ├── constants.ts │ │ └── index.ts │ ├── zircuit │ │ └── index.ts │ ├── zkbridge │ │ ├── contants.ts │ │ └── index.ts │ └── zksync │ │ └── index.ts ├── cli │ └── separateFiles.js ├── data │ ├── blacklist.ts │ ├── bridgeNetworkData.ts │ ├── importBridgeNetwork.ts │ └── types.ts ├── handlers │ ├── checkStaleBridges.ts │ ├── dailyAggregateAllAdapters.ts │ ├── getBridge.ts │ ├── getBridgeChains.ts │ ├── getBridgeStatsOnDay.ts │ ├── getBridgeVolume.ts │ ├── getBridges.ts │ ├── getLargeTransactions.ts │ ├── getLastBlocks.ts │ ├── getNetflows.ts │ ├── getTransactions.ts │ ├── runAdapter.ts │ ├── runAdapterByName.ts │ ├── runAdapterFromTo.ts │ ├── runAggregateAllAdapters.ts │ ├── runAllAdapters.ts │ ├── runAllAdaptersHistorical.ts │ ├── runHyperlane.ts │ ├── runInterSoon.ts │ ├── runLayerZero.ts │ └── runWormhole.ts ├── helpers │ ├── allium.ts │ ├── axelar.wip │ ├── blockscout.ts │ ├── bridgeAdapter.type.ts │ ├── btr.ts │ ├── cache.ts │ ├── etherscan.ts │ ├── eventParams.ts │ ├── l2scan.ts │ ├── mapofzones │ │ ├── IBCTxsPage │ │ │ └── __generated__ │ │ │ │ └── IBCTxsTable.query.generated.ts │ │ ├── base-types.ts │ │ └── index.ts │ ├── mapping.ts │ ├── merlin.ts │ ├── processTransactions.ts │ ├── solana.ts │ ├── sui.ts │ ├── tokenMappings.ts │ └── tron.ts ├── server │ ├── cron.ts │ ├── health.ts │ ├── index.ts │ ├── jobs │ │ ├── aggregateDailyVolume.ts │ │ ├── aggregateHourlyVolume.ts │ │ ├── runAdaptersFromTo.ts │ │ ├── runAggregateAllAdapter.ts │ │ ├── runAllAdapters.ts │ │ └── warmCache.ts │ └── startCron.ts └── utils │ ├── adapter.ts │ ├── aggregate.ts │ ├── blocks.ts │ ├── bridgeVolume.ts │ ├── cache.ts │ ├── constants.ts │ ├── date.ts │ ├── db.js │ ├── discord.ts │ ├── getRecordClosestToTimestamp.ts │ ├── insertConfigRows.ts │ ├── insertRecordedBlocks.ts │ ├── lambda-response.ts │ ├── normalizeChain.ts │ ├── prices.ts │ ├── provider.ts │ ├── runAdapterHistorical.ts │ ├── s3.ts │ ├── testAdapterHistorical.ts │ ├── testDailyVolume.ts │ ├── testadapter.ts │ ├── testaggregate.ts │ ├── types.ts │ ├── wrap.ts │ └── wrappa │ └── postgres │ ├── query.ts │ └── write.ts ├── tsconfig.json ├── vite.config.ts └── webpack.config.js /.env.test: -------------------------------------------------------------------------------- 1 | DB_URL="postgresql://postgres:password@localhost:5433/mydatabase" -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Deploy 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: Get Node.js 13 | uses: actions/setup-node@v1 14 | with: 15 | node-version: "18" 16 | - run: npm ci 17 | - name: Type Check 18 | run: npm run ts 19 | - name: Deploy infrastructure stack 20 | run: npm run deploy:prod 21 | env: 22 | AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} 23 | AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} 24 | PSQL_URL: ${{ secrets.PSQL_URL }} 25 | PSQL_USERNAME: ${{ secrets.PSQL_USERNAME }} 26 | PSQL_PW: ${{ secrets.PSQL_PW }} 27 | BSC_RPC: ${{ secrets.BSC_RPC }} 28 | CELO_RPC: ${{ secrets.CELO_RPC }} 29 | ETHEREUM_RPC: ${{ secrets.ETHEREUM_RPC }} 30 | OPTIMISM_RPC: ${{ secrets.OPTIMISM_RPC }} 31 | AURORA_RPC: ${{ secrets.AURORA_RPC }} 32 | ARBITRUM_RPC: ${{ secrets.ARBITRUM_RPC }} 33 | RSK_ARCHIVAL_RPC: ${{ secrets.RSK_ARCHIVAL_RPC }} 34 | DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }} 35 | ALLIUM_API_KEY: ${{ secrets.ALLIUM_API_KEY }} 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .nvim 3 | .webpack 4 | .serverless 5 | src/adapters/adapterchecklist.txt 6 | src/adapters/multichain/anyswapaddresses.json 7 | src/adapters/multichain/chainid.ts 8 | src/adapters/multichain/process.ts 9 | src/adapters/multichain/response.json 10 | src/adapters/multichain/test.ts 11 | src/adapters/portal/junk.ts 12 | src/adapters/newchainchecklist.txt 13 | src/utils/format.ts 14 | src/utils/getts.ts 15 | src/adapters/axelar/api.json 16 | src/adapters/axelar/process.ts 17 | src/utils/runall.ts 18 | src/utils/filljobs.ts 19 | src/utils/filljobs2.ts 20 | 21 | .esbuild 22 | 23 | .env 24 | 25 | .idea 26 | 27 | .vscoded 28 | 29 | dist -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "tabWidth": 2 4 | } 5 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM postgres:latest 2 | 3 | ENV POSTGRES_DB=mydatabase 4 | ENV POSTGRES_USER=postgres 5 | ENV POSTGRES_PASSWORD=password 6 | 7 | COPY ./sql/data.sql /docker-entrypoint-initdb.d/ -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: "bridges-server", 5 | script: "dist/index.cjs", 6 | instances: 1, 7 | autorestart: true, 8 | watch: false, 9 | max_memory_restart: "1G", 10 | env: { 11 | NODE_ENV: "production", 12 | }, 13 | error_file: "logs/err.log", 14 | out_file: "logs/out.log", 15 | restart_delay: 4000, 16 | kill_timeout: 3000, 17 | exp_backoff_restart_delay: 100, 18 | max_restarts: 10, 19 | }, 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridges-server", 3 | "scripts": { 4 | "deploy:prod": "sls deploy --stage prod", 5 | "serve": "sls offline start", 6 | "build:aws": "sls package", 7 | "test": "tsx ./src/adapters/test.ts", 8 | "test-txs": "tsx ./src/utils/testAdapterHistorical.ts", 9 | "ts": "tsc --noEmit", 10 | "adapter": "export $(cat .env | xargs) && tsx ./src/utils/runAdapterHistorical.ts", 11 | "aggregate": "export $(cat .env | xargs) && tsx ./src/utils/testaggregate.ts", 12 | "daily-volume": "export $(cat .env | xargs) && tsx ./src/utils/testDailyVolume.ts", 13 | "dev": "vite", 14 | "build": "vite build", 15 | "start": "node ./dist/index.js", 16 | "start:dev": "node ./dist/index.js", 17 | "start:cron": "node ./dist/startCron.js" 18 | }, 19 | "devDependencies": { 20 | "@types/async-retry": "^1.4.8", 21 | "@types/aws-lambda": "^8.10.101", 22 | "@types/js-yaml": "^4.0.9", 23 | "@types/node": "^18.19.67", 24 | "@types/node-fetch": "^2.6.2", 25 | "@types/object-hash": "^3.0.6", 26 | "@types/retry": "^0.12.5", 27 | "eslint-config-prettier": "^8.3.0", 28 | "prettier": "^2.5.1", 29 | "serverless": "^3.21.0", 30 | "serverless-esbuild": "^1.54.6", 31 | "serverless-offline": "^12.0.0", 32 | "serverless-prune-plugin": "^2.0.1", 33 | "vite": "^6.0.1" 34 | }, 35 | "dependencies": { 36 | "@aws-sdk/client-lambda": "^3.637.0", 37 | "@aws-sdk/client-s3": "^3.749.0", 38 | "@aws-sdk/client-sts": "^3.749.0", 39 | "@aws-sdk/credential-providers": "^3.749.0", 40 | "@defillama/sdk": "^5.0.137", 41 | "@fastify/cors": "^9.0.1", 42 | "@graphql-typed-document-node/core": "^3.2.0", 43 | "@solana/web3.js": "^1.87.3", 44 | "async-retry": "^1.3.1", 45 | "axios": "^0.21.0", 46 | "axios-rate-limit": "^1.3.0", 47 | "bignumber.js": "^9.0.1", 48 | "cron": "^3.2.1", 49 | "dayjs": "^1.11.13", 50 | "dotenv": "^8.2.0", 51 | "esbuild": "0.23.1", 52 | "ethers": "^5", 53 | "fastify": "^4.26.2", 54 | "graphql": "^16.0.0", 55 | "graphql-request": "^6.1.0", 56 | "ioredis": "^5.5.0", 57 | "node-fetch": "^2.6.7", 58 | "object-hash": "^3.0.0", 59 | "postgres": "^3.2.4", 60 | "tron-format-address": "^0.1.11", 61 | "tsx": "^4.7.1", 62 | "typescript": "^5.4.2" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /resources/api-gateway-errors.yml: -------------------------------------------------------------------------------- 1 | Resources: 2 | GatewayResponseDefault4XX: 3 | Type: "AWS::ApiGateway::GatewayResponse" 4 | Properties: 5 | ResponseParameters: 6 | gatewayresponse.header.Access-Control-Allow-Origin: "'*'" 7 | gatewayresponse.header.Access-Control-Allow-Headers: "'*'" 8 | ResponseType: DEFAULT_4XX 9 | RestApiId: 10 | Ref: "ApiGatewayRestApi" 11 | GatewayResponseDefault5XX: 12 | Type: "AWS::ApiGateway::GatewayResponse" 13 | Properties: 14 | ResponseParameters: 15 | gatewayresponse.header.Access-Control-Allow-Origin: "'*'" 16 | gatewayresponse.header.Access-Control-Allow-Headers: "'*'" 17 | ResponseType: DEFAULT_5XX 18 | RestApiId: 19 | Ref: "ApiGatewayRestApi" 20 | -------------------------------------------------------------------------------- /soroban_rpc.sqlite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DefiLlama/bridges-server/d1222e8a085c12de7757167ccc30a4320d64ce61/soroban_rpc.sqlite -------------------------------------------------------------------------------- /src/adapters/allbridge-classic/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 5 | 6 | const bridgeAddresses = { 7 | ethereum: "0xBBbD1BbB4f9b936C3604906D7592A644071dE884", 8 | avax: "0xBBbD1BbB4f9b936C3604906D7592A644071dE884", 9 | bsc: "0xBBbD1BbB4f9b936C3604906D7592A644071dE884", 10 | fantom: "0xBBbD1BbB4f9b936C3604906D7592A644071dE884", 11 | polygon: "0xBBbD1BbB4f9b936C3604906D7592A644071dE884", 12 | } as { [chain: string]: string }; 13 | 14 | const constructParams = (chain: string) => { 15 | let eventParams = [] as PartialContractEventParams[]; 16 | const bridgeAddress = bridgeAddresses[chain]; 17 | const depositParams = constructTransferParams(bridgeAddress, true, { 18 | excludeFrom: [bridgeAddress], 19 | }); 20 | // const withdrawParams = constructTransferParams(bridgeAddress, false, { 21 | // excludeTo: [bridgeAddress], 22 | // }); 23 | const withdrawParams = { 24 | target: bridgeAddress, 25 | topic: "Received(address,address,uint256,uint128,bytes4)", 26 | abi: [ 27 | "event Received(address indexed recipient, address token, uint256 amount, uint128 indexed lockId, bytes4 source)", 28 | ], 29 | logKeys: { 30 | blockNumber: "blockNumber", 31 | txHash: "transactionHash", 32 | }, 33 | argKeys: { 34 | amount: "amount", 35 | token: "token", 36 | to: "recipient", 37 | }, 38 | fixedEventData: { 39 | from: bridgeAddress, 40 | }, 41 | isDeposit: false, 42 | }; 43 | eventParams.push(depositParams, withdrawParams); 44 | return async (fromBlock: number, toBlock: number) => 45 | getTxDataFromEVMEventLogs("allbridge-classic", chain as Chain, fromBlock, toBlock, eventParams); 46 | }; 47 | 48 | const adapter: BridgeAdapter = { 49 | ethereum: constructParams("ethereum"), 50 | polygon: constructParams("polygon"), 51 | fantom: constructParams("fantom"), 52 | avalanche: constructParams("avax"), 53 | bsc: constructParams("bsc"), 54 | }; 55 | 56 | export default adapter; 57 | -------------------------------------------------------------------------------- /src/adapters/avalanche-btc/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BridgeAdapter, 3 | ContractEventParams, 4 | } from "../../helpers/bridgeAdapter.type"; 5 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 6 | 7 | // 0x152b9d0FdC40C096757F570A51E494bd4b943E50 is BTC.b 8 | 9 | const depositEventParams: ContractEventParams = { 10 | target: "0x152b9d0FdC40C096757F570A51E494bd4b943E50", 11 | topic: "Unwrap(uint256,uint256)", 12 | abi: [ 13 | "event Unwrap(uint256 amount, uint256 chainId)", 14 | ], 15 | logKeys: { 16 | blockNumber: "blockNumber", 17 | txHash: "transactionHash", 18 | }, 19 | txKeys: { 20 | from: "from" 21 | }, 22 | argKeys: { 23 | amount: "amount", 24 | }, 25 | fixedEventData: { 26 | to: "0x152b9d0FdC40C096757F570A51E494bd4b943E50", 27 | token: "0x152b9d0FdC40C096757F570A51E494bd4b943E50" 28 | }, 29 | isDeposit: true, 30 | }; 31 | 32 | 33 | const withdrawalEventParams: ContractEventParams = { 34 | target: "0x152b9d0FdC40C096757F570A51E494bd4b943E50", 35 | topic: "Mint(address,uint256,address,uint256,bytes32,uint256)", 36 | abi: [ 37 | "event Mint(address to, uint256 amount, address feeAddress, uint256 feeAmount, bytes32 originTxId, uint256 originOutputIndex)", 38 | ], 39 | logKeys: { 40 | blockNumber: "blockNumber", 41 | txHash: "transactionHash", 42 | }, 43 | argKeys: { 44 | to: "to", 45 | amount: "amount", 46 | }, 47 | fixedEventData: { 48 | from: "0x152b9d0FdC40C096757F570A51E494bd4b943E50", 49 | token: "0x152b9d0FdC40C096757F570A51E494bd4b943E50" 50 | }, 51 | isDeposit: false, 52 | }; 53 | 54 | const constructParams = () => { 55 | const eventParams = [ 56 | depositEventParams, 57 | withdrawalEventParams 58 | ]; 59 | return async (fromBlock: number, toBlock: number) => 60 | getTxDataFromEVMEventLogs("avalanche-btc", "avax", fromBlock, toBlock, eventParams); 61 | }; 62 | 63 | const adapter: BridgeAdapter = { 64 | avalanche: constructParams(), 65 | }; 66 | 67 | export default adapter; 68 | -------------------------------------------------------------------------------- /src/adapters/avalanche/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BridgeAdapter, 3 | PartialContractEventParams, 4 | } from "../../helpers/bridgeAdapter.type"; 5 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 6 | import { constructTransferParams } from "../../helpers/eventParams"; 7 | 8 | // 0x8EB8a3b98659Cce290402893d0123abb75E3ab28 is Avalanche: Bridge (EOA) 9 | 10 | const depositEventParams: PartialContractEventParams = 11 | constructTransferParams( 12 | "0x8EB8a3b98659Cce290402893d0123abb75E3ab28", 13 | true 14 | ); 15 | 16 | const withdrawalEventParams: PartialContractEventParams = 17 | constructTransferParams( 18 | "0x8EB8a3b98659Cce290402893d0123abb75E3ab28", 19 | false 20 | ); 21 | 22 | const constructParams = () => { 23 | const eventParams = [ 24 | depositEventParams, 25 | withdrawalEventParams 26 | ]; 27 | return async (fromBlock: number, toBlock: number) => 28 | getTxDataFromEVMEventLogs("avalanche", "ethereum", fromBlock, toBlock, eventParams); 29 | }; 30 | 31 | const adapter: BridgeAdapter = { 32 | ethereum: constructParams(), 33 | }; 34 | 35 | export default adapter; 36 | -------------------------------------------------------------------------------- /src/adapters/axelar-satellite/constant.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | export const gatewayAddresses = { 4 | ethereum: "0x4F4495243837681061C4743b74B3eEdf548D56A5", 5 | bsc: "0x304acf330bbE08d1e512eefaa92F6a57871fD895", 6 | polygon: "0x6f015F16De9fC8791b234eF68D486d2bF203FBA8", 7 | avax: "0x5029C0EFf6C34351a0CEc334542cDb22c7928f78", 8 | fantom: "0x304acf330bbE08d1e512eefaa92F6a57871fD895", 9 | arbitrum: "0xe432150cce91c13a887f7D836923d5597adD8E31", 10 | optimism: "0xe432150cce91c13a887f7D836923d5597adD8E31", 11 | base: "0xe432150cce91c13a887f7D836923d5597adD8E31", 12 | linea: "0xe432150cce91c13a887f7D836923d5597adD8E31", 13 | moonbeam: "0x4F4495243837681061C4743b74B3eEdf548D56A5", 14 | celo: "0xe432150cce91c13a887f7D836923d5597adD8E31", 15 | kava: "0xe432150cce91c13a887f7D836923d5597adD8E31", 16 | filecoin: "0xe432150cce91c13a887f7D836923d5597adD8E31", 17 | mantle: "0xe432150cce91c13a887f7D836923d5597adD8E31" 18 | } as { 19 | [chain: string]: string; 20 | }; 21 | 22 | // The same address for all chains 23 | export const depositServiceAddress = "0xc1DCb196BA862B337Aa23eDA1Cb9503C0801b955"; 24 | 25 | export const withdrawParams = (from: string, to: string) => ({ 26 | target: "", 27 | topic: "Transfer(address,address,uint256)", 28 | topics: [ 29 | ethers.utils.id("Transfer(address,address,uint256)"), 30 | ethers.utils.hexZeroPad(from, 32), 31 | ethers.utils.hexZeroPad(to, 32), 32 | ], 33 | abi: ["event Transfer(address indexed from, address indexed to, uint256 value)"], 34 | logKeys: { 35 | blockNumber: "blockNumber", 36 | txHash: "transactionHash", 37 | }, 38 | argKeys: { 39 | to: "to", 40 | amount: "value", 41 | }, 42 | fixedEventData: { 43 | token: "", 44 | from: "", 45 | }, 46 | functionSignatureFilter: { 47 | includeSignatures: ["0x09c5ea"], 48 | }, 49 | isDeposit: false, 50 | }); 51 | 52 | export const wrapParams = (from: string, to: string) => ({ 53 | ...withdrawParams(from, to), 54 | functionSignatureFilter: { 55 | includeSignatures: ["0xcf85fb"], 56 | }, 57 | isDeposit: true, 58 | }); 59 | -------------------------------------------------------------------------------- /src/adapters/axelar-satellite/type.ts: -------------------------------------------------------------------------------- 1 | export interface AxelarTransfer { 2 | deposit_address: string; 3 | destination_chain: string; 4 | source_chain: string; 5 | sender_address: string; 6 | recipient_address: string; 7 | denom: string; 8 | } 9 | 10 | export interface DepositAddressTransfer extends AxelarTransfer {} 11 | 12 | export interface WrapTransfer extends AxelarTransfer { 13 | tx_hash_wrap: string; 14 | } 15 | 16 | export interface FetchDepositTransfersOptions { 17 | isDeposit: boolean; 18 | type?: "deposit_address" | "wrap"; 19 | size?: number; 20 | } 21 | 22 | export interface FetchDepositTransfersResponse { 23 | data: { 24 | link: DepositAddressTransfer; 25 | wrap: DepositAddressTransfer; 26 | }[]; 27 | } 28 | -------------------------------------------------------------------------------- /src/adapters/axelar/utils.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import retry from "async-retry"; 3 | 4 | export const getItsTokens = () => 5 | retry(() => 6 | fetch("https://api.axelarscan.io/api/getITSAssets", { 7 | method: "GET", 8 | headers: { "Content-Type": "application/json" }, 9 | }).then((res: any) => res.json()) 10 | ); 11 | 12 | 13 | export const getAssets = () => 14 | retry(() => 15 | fetch("https://api.axelarscan.io/api/getAssets", { 16 | method: "GET", 17 | headers: { "Content-Type": "application/json" }, 18 | }).then((res: any) => res.json()) 19 | ); 20 | 21 | // TODO 22 | // custom tokens dont seem to be getting queried for example token from this tx 23 | // https://axelarscan.io/gmp/0x83d2af2f18c63349d755ce2f3fd3ff4a7990ebd0e61458200f60504e198c843d -------------------------------------------------------------------------------- /src/adapters/base/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | import { getTxsBlockRangeEtherscan } from "../../helpers/etherscan"; 4 | 5 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 6 | const BASE_PORTAL = "0x49048044D57e1C92A77f79988d21Fa8fAF74E97e"; 7 | const BASE_BRIDGE = "0x3154Cf16ccdb4C6d922629664174b904d80F2C35"; 8 | 9 | const erc20DepositParams: PartialContractEventParams = { 10 | target: BASE_BRIDGE, 11 | topic: "ERC20BridgeInitiated(address,address,address,address,uint256,bytes)", 12 | abi: [ 13 | "event ERC20BridgeInitiated(address indexed localToken, address indexed remoteToken, address indexed from, address to, uint256 amount, bytes extraData)", 14 | ], 15 | logKeys: { 16 | blockNumber: "blockNumber", 17 | txHash: "transactionHash", 18 | }, 19 | argKeys: { 20 | from: "from", 21 | amount: "amount", 22 | token: "localToken", 23 | }, 24 | fixedEventData: { 25 | to: BASE_BRIDGE, 26 | }, 27 | isDeposit: true, 28 | }; 29 | 30 | const erc20WithdrawParams: PartialContractEventParams = { 31 | target: BASE_BRIDGE, 32 | topic: "ERC20WithdrawalFinalized(address,address,address,address,uint256,bytes)", 33 | abi: [ 34 | "event ERC20WithdrawalFinalized(address indexed l1Token, address indexed l2Token, address indexed from, address to, uint256 amount, bytes extraData)", 35 | ], 36 | logKeys: { 37 | blockNumber: "blockNumber", 38 | txHash: "transactionHash", 39 | }, 40 | argKeys: { 41 | to: "to", 42 | amount: "amount", 43 | token: "l1Token", 44 | }, 45 | fixedEventData: { 46 | from: BASE_BRIDGE, 47 | }, 48 | isDeposit: false, 49 | }; 50 | 51 | const constructParams = () => { 52 | const eventParams = [erc20WithdrawParams, erc20DepositParams]; 53 | return async (fromBlock: number, toBlock: number) => { 54 | const eventLogsRes = await getTxDataFromEVMEventLogs("base", "ethereum", fromBlock, toBlock, eventParams); 55 | const txs = await getTxsBlockRangeEtherscan("ethereum", BASE_PORTAL, fromBlock, toBlock); 56 | const depositEvents = txs 57 | .filter((tx: any) => tx?.methodId === "0xe9e05c42") 58 | .map((tx: any) => { 59 | const event = { 60 | txHash: tx.hash, 61 | blockNumber: +tx.blockNumber, 62 | from: tx.from, 63 | to: tx.to, 64 | token: WETH, 65 | amount: tx.value, 66 | isDeposit: true, 67 | }; 68 | return event; 69 | }); 70 | 71 | const withdrawalEvents = txs 72 | .filter((tx: any) => tx?.methodId === "0x8c3152e9") 73 | .map((tx: any) => { 74 | const event = { 75 | txHash: tx.hash, 76 | blockNumber: +tx.blockNumber, 77 | from: BASE_PORTAL, 78 | to: tx.to, 79 | token: WETH, 80 | amount: tx.value, 81 | isDeposit: false, 82 | }; 83 | return event; 84 | }); 85 | return [...eventLogsRes, ...depositEvents, ...withdrawalEvents]; 86 | }; 87 | }; 88 | 89 | const adapter: BridgeAdapter = { 90 | ethereum: constructParams(), 91 | }; 92 | 93 | export default adapter; 94 | -------------------------------------------------------------------------------- /src/adapters/beamer/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | const nullAddress = "0x0000000000000000000000000000000000000000"; 5 | 6 | const contractAddresses = { 7 | ethereum: { 8 | requestManager: "0x7faEa6562a6cE991149F0167baF283E9aAc7Dc50", 9 | fillManager: "0xD5EF34B499b6d64817CC70C3b0B8D9f807F06C29", 10 | }, 11 | arbitrum: { 12 | requestManager: "0x13bAF73f48FCF6A8aAb8431CA3A38c624cdfd8F3", 13 | fillManager: "0x27f72B9745CDE0fbAFe0A0d1119D75f4082bFc47", 14 | }, 15 | optimism: { 16 | requestManager: "0x124198789EF8d82050E620De2b73332C3c6C2eD4", 17 | fillManager: "0x889aa3c5b5298d70613373F25Ef66Fede25B4de1", 18 | }, 19 | } as { 20 | [chain: string]: { 21 | requestManager: string; 22 | fillManager: string; 23 | }; 24 | }; 25 | 26 | const constructParams = (chain: string) => { 27 | let eventParams = [] as PartialContractEventParams[]; 28 | const requestManager = contractAddresses[chain].requestManager; 29 | const fillManager = contractAddresses[chain].fillManager; 30 | 31 | const deposit: PartialContractEventParams = { 32 | target: requestManager, 33 | topic: "RequestCreated(bytes32,uint256,address,address,address,address,uint256,uint96,uint32,uint256,uint256)", 34 | abi: [ 35 | "event RequestCreated(bytes32 indexed requestId,uint256 targetChainId,address sourceTokenAddress,address targetTokenAddress,address indexed sourceAddress,address targetAddress,uint256 amount,uint96 nonce,uint32 validUntil,uint256 lpFee,uint256 protocolFee)", 36 | ], 37 | logKeys: { 38 | blockNumber: "blockNumber", 39 | txHash: "transactionHash", 40 | }, 41 | argKeys: { 42 | amount: "amount", 43 | token: "sourceTokenAddress", 44 | from: "targetAddress", 45 | }, 46 | fixedEventData: { 47 | to: requestManager, 48 | }, 49 | isDeposit: true, 50 | }; 51 | 52 | const withdraw: PartialContractEventParams = { 53 | target: fillManager, 54 | topic: "RequestFilled(bytes32,bytes32,uint256,address,address,uint256)", 55 | abi: [ 56 | "event RequestFilled(bytes32 indexed requestId,bytes32 fillId,uint256 indexed sourceChainId,address indexed targetTokenAddress,address filler,uint256 amount)", 57 | ], 58 | logKeys: { 59 | blockNumber: "blockNumber", 60 | txHash: "transactionHash", 61 | }, 62 | argKeys: { 63 | amount: "amount", 64 | token: "targetTokenAddress", 65 | from: "filler", 66 | }, 67 | fixedEventData: { 68 | to: nullAddress 69 | }, 70 | isDeposit: false, 71 | }; 72 | 73 | eventParams.push(deposit, withdraw); 74 | return async (fromBlock: number, toBlock: number) => 75 | getTxDataFromEVMEventLogs("beamer", chain as Chain, fromBlock, toBlock, eventParams); 76 | }; 77 | 78 | const adapter: BridgeAdapter = { 79 | ethereum: constructParams("ethereum"), 80 | arbitrum: constructParams("arbitrum"), 81 | optimism: constructParams("optimism"), 82 | }; 83 | 84 | export default adapter; 85 | -------------------------------------------------------------------------------- /src/adapters/circle/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | 5 | /* 6 | https://developers.circle.com/stablecoins/docs/evm-smart-contracts 7 | */ 8 | 9 | const contracts = { 10 | ethereum: { TokenMessenger: "0xBd3fa81B58Ba92a82136038B25aDec7066af3155" }, 11 | optimism: { TokenMessenger: "0x2B4069517957735bE00ceE0fadAE88a26365528f" }, 12 | polygon: { TokenMessenger: "0x9daF8c91AEFAE50b9c0E69629D3F6Ca40cA3B3FE" }, 13 | base: { TokenMessenger: "0x1682Ae6375C4E4A97e4B583BC394c861A46D8962" }, 14 | arbitrum: { TokenMessenger: "0x19330d10D9Cc8751218eaf51E8885D058642E08A" }, 15 | avax: { TokenMessenger: "0x6B25532e1060CE10cc3B0A99e5683b91BFDe6982" }, 16 | } as const; 17 | type SupportedChains = keyof typeof contracts; 18 | 19 | const depositParams: PartialContractEventParams = { 20 | target: "", 21 | topic: "DepositForBurn(uint64,address,uint256,address,bytes32,uint32,bytes32,bytes32)", 22 | abi: [ 23 | "event DepositForBurn(uint64 indexed nonce, address indexed burnToken, uint256 amount, address indexed depositor, bytes32 mintRecipient, uint32 destinationDomain, bytes32 destinationTokenMessenger, bytes32 destinationCaller)", 24 | ], 25 | logKeys: { 26 | blockNumber: "blockNumber", 27 | txHash: "transactionHash", 28 | }, 29 | argKeys: { 30 | amount: "amount", 31 | from: "depositor", 32 | to: "mintRecipient", 33 | token: "burnToken", 34 | }, 35 | isDeposit: true, 36 | }; 37 | 38 | const withdrawParams: PartialContractEventParams = { 39 | target: "", 40 | topic: "MintAndWithdraw(address,uint256,address)", 41 | abi: [ 42 | "event MintAndWithdraw(address indexed mintRecipient, uint256 amount, address indexed mintToken)", 43 | ], 44 | logKeys: { 45 | blockNumber: "blockNumber", 46 | txHash: "transactionHash", 47 | }, 48 | argKeys: { 49 | amount: "amount", 50 | to: "mintRecipient", 51 | from: "mintRecipient", 52 | token: "mintToken", 53 | }, 54 | isDeposit: false, 55 | }; 56 | 57 | const constructParams = (chain: Chain) => { 58 | 59 | const chainConfig = contracts[chain as SupportedChains]; 60 | 61 | const eventParams: PartialContractEventParams[] = [ 62 | { ...depositParams, target: chainConfig.TokenMessenger }, 63 | { ...withdrawParams, target: chainConfig.TokenMessenger } 64 | ]; 65 | 66 | return async (fromBlock: number, toBlock: number) => 67 | getTxDataFromEVMEventLogs("circle", chain, fromBlock, toBlock, eventParams); 68 | }; 69 | 70 | const adapter: BridgeAdapter = { 71 | ethereum: constructParams("ethereum"), 72 | optimism: constructParams("optimism"), 73 | polygon: constructParams("polygon"), 74 | base: constructParams("base"), 75 | arbitrum: constructParams("arbitrum"), 76 | avalanche: constructParams("avax") 77 | }; 78 | 79 | export default adapter; 80 | -------------------------------------------------------------------------------- /src/adapters/crowdswap/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | 5 | 6 | const gatewayAddresses = { 7 | ethereum: "0x22715dfF101D33B2a14a4834f7C527902Bc42899", 8 | bsc: "0x587AaA2150AD416bAD6b9919FDfF2D78BE11383B", 9 | polygon: "0x9092fCF5Ea1E22f2922eEa132D2931CDd795ab53", 10 | optimism: "0x1B3aE33ff0241999854C05B0CdF821DE55A4404A", 11 | arbitrum: "0x99a68649E927774680e9D3387BF8cCbF93B45230", 12 | rsk: "0x9Ff74eEA1e7f0f8eE437b70d68F7Cdc1a1030642", 13 | base: "0xCbe00062dd0a9d638F724a2771153bEd9Ba7E123" 14 | } as { 15 | [chain: string]: string; 16 | }; 17 | 18 | 19 | let routerAddresses = {} as { 20 | [chain: string]: string[]; 21 | }; 22 | 23 | const activeChains = [ 24 | "ethereum", 25 | "bsc", 26 | "polygon", 27 | "optimism", 28 | "arbitrum", 29 | "base", 30 | // "rsk" 31 | ]; 32 | 33 | for(const chain of activeChains){ 34 | if(!routerAddresses[chain]){ 35 | routerAddresses[chain] = ["0x549D287218E5fc9D07A91Fe2e1337D5c21B808B2","0x30462a4863a3db9233006a87320a8e07c4a71a36"] 36 | } 37 | } 38 | 39 | 40 | const constructParams = (chain: string) => { 41 | let eventParams = [] as PartialContractEventParams[]; 42 | const routers = routerAddresses[chain]; 43 | 44 | for (const router of Object.values(routers)) { 45 | const deposit :PartialContractEventParams = { 46 | target: router, 47 | topic: "MessageSent(bytes32,uint256,uint256,uint256,uint256,address,address,address,uint64)", 48 | abi: [ 49 | "event MessageSent(bytes32 indexed messageId, uint256 sourceAmount, uint256 feeAmount, uint256 destinationAmount, uint256 destinationMinAmount, address sourceTokenAddress, address destinationTokenAddress, address sender, uint64 indexed destinationChainId)" 50 | ], 51 | isDeposit: true, 52 | logKeys: { 53 | blockNumber: "blockNumber", 54 | txHash: "transactionHash", 55 | }, 56 | argKeys: { 57 | amount: "sourceAmount", 58 | token: "sourceTokenAddress" 59 | }, 60 | fixedEventData: { 61 | to: gatewayAddresses[chain], 62 | from: router, 63 | }, 64 | }; 65 | 66 | const withdraw :PartialContractEventParams = { 67 | target: router, 68 | topic: "MessageCompleted(bytes32,uint256,address,address)", 69 | abi: ["event MessageCompleted(bytes32 indexed messageId, uint256 destinationAmount, address destinationTokenAddress, address receiver)"], 70 | isDeposit: false, // event type 71 | logKeys: { 72 | blockNumber: "blockNumber", 73 | txHash: "transactionHash", 74 | }, 75 | argKeys: { 76 | amount: "destinationAmount", 77 | token: "destinationTokenAddress", 78 | }, 79 | fixedEventData: { 80 | from: router, 81 | to: gatewayAddresses[chain], 82 | }, 83 | }; 84 | 85 | eventParams.push(deposit, withdraw); 86 | } 87 | return async (fromBlock: number, toBlock: number) => 88 | await getTxDataFromEVMEventLogs("crowdswap", chain as Chain, fromBlock, toBlock, eventParams); 89 | }; 90 | 91 | const adapter: BridgeAdapter = { 92 | polygon: constructParams("polygon"), 93 | bsc: constructParams("bsc"), 94 | ethereum: constructParams("ethereum"), 95 | arbitrum: constructParams("arbitrum"), 96 | optimism: constructParams("optimism"), 97 | base: constructParams("base"), 98 | // rsk: constructParams("rsk"), 99 | }; 100 | 101 | export default adapter; 102 | -------------------------------------------------------------------------------- /src/adapters/eclipse/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { constructTransferParams } from "../../helpers/eventParams"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | 5 | // 0x2B08D7cF7EafF0f5f6623d9fB09b080726D4be11 is Eclipse Canonical Bridge 6 | 7 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 8 | 9 | const depositEventParams: PartialContractEventParams = { 10 | target: "0x2B08D7cF7EafF0f5f6623d9fB09b080726D4be11", 11 | topic: "Deposited(address,bytes32,uint256,uint256)", 12 | abi: [ 13 | "event Deposited(address indexed sender, bytes32 indexed recipient, uint256 amountWei, uint256 amountLamports)", 14 | ], 15 | argKeys: { 16 | from: "sender", 17 | to: "recipient", 18 | amount: "amountWei", 19 | }, 20 | fixedEventData: { 21 | token: WETH, 22 | }, 23 | isDeposit: true, 24 | }; 25 | 26 | const withdrawalEventParams: PartialContractEventParams = { 27 | target: "0x2B08D7cF7EafF0f5f6623d9fB09b080726D4be11", 28 | topic: "WithdrawClaimed(address,bytes32,bytes32,WithdrawMessage)", 29 | abi: [ 30 | "event WithdrawClaimed(address indexed receiver, bytes32 indexed remoteSender, bytes32 indexed messageHash, tuple(bytes32 from, address destination, uint256 amountWei, uint64 withdrawId, address feeReceiver, uint256 feeWei) message)", 31 | ], 32 | argKeys: { 33 | from: "remoteSender", 34 | to: "receiver", 35 | amount: "message.amountWei", 36 | }, 37 | fixedEventData: { 38 | token: WETH, 39 | }, 40 | isDeposit: false, 41 | }; 42 | 43 | const constructParams = () => { 44 | const eventParams = [depositEventParams, withdrawalEventParams]; 45 | return async (fromBlock: number, toBlock: number) => 46 | getTxDataFromEVMEventLogs("eclipse", "ethereum", fromBlock, toBlock, eventParams); 47 | }; 48 | 49 | const adapter: BridgeAdapter = { 50 | ethereum: constructParams(), 51 | }; 52 | 53 | export default adapter; 54 | -------------------------------------------------------------------------------- /src/adapters/fuel/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 5 | 6 | const FUEL_BRIDGE_ADDRESS_ERC20 = "0xa4cA04d02bfdC3A2DF56B9b6994520E69dF43F67"; 7 | const FUEL_MESSAGING_ADDRESS = "0xAEB0c00D0125A8a788956ade4f4F12Ead9f65DDf"; 8 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 9 | 10 | const erc20DepositEventParams: PartialContractEventParams = constructTransferParams(FUEL_BRIDGE_ADDRESS_ERC20, true); 11 | 12 | const erc20WithdrawalEventParams: PartialContractEventParams = constructTransferParams( 13 | FUEL_BRIDGE_ADDRESS_ERC20, 14 | false 15 | ); 16 | 17 | function bytes32ToAddress(bytes32: string) { 18 | return "0x" + bytes32.slice(26); 19 | } 20 | 21 | const ethWithdrawalParams: PartialContractEventParams = { 22 | target: FUEL_MESSAGING_ADDRESS, 23 | topic: "MessageRelayed(bytes32,bytes32,bytes32,uint64)", 24 | abi: [ 25 | "event MessageRelayed (bytes32 indexed messageId, bytes32 indexed sender, bytes32 indexed recipient, uint64 amount)", 26 | ], 27 | logKeys: { 28 | blockNumber: "blockNumber", 29 | txHash: "transactionHash", 30 | }, 31 | argKeys: { 32 | to: "recipient", 33 | amount: "amount", 34 | }, 35 | fixedEventData: { 36 | from: FUEL_MESSAGING_ADDRESS, 37 | token: WETH, 38 | }, 39 | isDeposit: false, 40 | }; 41 | 42 | const ethDepositParams: PartialContractEventParams = { 43 | target: FUEL_MESSAGING_ADDRESS, 44 | topic: "MessageSent(bytes32,bytes32,uint256,uint64,bytes)", 45 | abi: [ 46 | "event MessageSent (bytes32 indexed sender, bytes32 indexed recipient, uint256 indexed nonce, uint64 amount, bytes data)", 47 | ], 48 | logKeys: { 49 | blockNumber: "blockNumber", 50 | txHash: "transactionHash", 51 | }, 52 | argKeys: { 53 | amount: "amount", 54 | from: "sender", 55 | }, 56 | argGetters: { 57 | from: (logArgs: any) => bytes32ToAddress(logArgs.sender), 58 | }, 59 | fixedEventData: { 60 | to: FUEL_MESSAGING_ADDRESS, 61 | token: WETH, 62 | }, 63 | isDeposit: true, 64 | }; 65 | 66 | const constructParams = (chain: string) => { 67 | let eventParams = [ 68 | ethDepositParams, 69 | ethWithdrawalParams, 70 | erc20DepositEventParams, 71 | erc20WithdrawalEventParams, 72 | ] as PartialContractEventParams[]; 73 | 74 | return async (fromBlock: number, toBlock: number) => 75 | getTxDataFromEVMEventLogs("fuel", chain as Chain, fromBlock, toBlock, eventParams); 76 | }; 77 | 78 | const adapter: BridgeAdapter = { 79 | ethereum: constructParams("ethereum"), 80 | }; 81 | 82 | export default adapter; 83 | -------------------------------------------------------------------------------- /src/adapters/garden/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 5 | 6 | const contractAddresses = { 7 | ethereum: { 8 | WBTC: "0x795dcb58d1cd4789169d5f938ea05e17eceb68ca", 9 | USDC: "0xd8a6e3fca403d79b6ad6216b60527f51cc967d39", 10 | cbBTC: "0xeae7721d779276eb0f5837e2fe260118724a2ba4", 11 | iBTC: "0xDC74a45e86DEdf1fF7c6dac77e0c2F082f9E4F72", 12 | }, 13 | arbitrum: { 14 | WBTC: "0x6b6303fab8ec7232b4f2a7b9fa58e5216f608fcb", 15 | USDC: "0xeae7721d779276eb0f5837e2fe260118724a2ba4", 16 | iBTC: "0xdc74a45e86dedf1ff7c6dac77e0c2f082f9e4f72", 17 | }, 18 | base: { 19 | cbBTC: "0xeae7721d779276eb0f5837e2fe260118724a2ba4", 20 | USDC: "0xd8a6e3fca403d79b6ad6216b60527f51cc967d39", 21 | }, 22 | unichain: { 23 | WBTC: "0xD8a6E3FCA403d79b6AD6216b60527F51cc967D39", 24 | USDC: "0x795Dcb58d1cd4789169D5F938Ea05E17ecEB68cA", 25 | }, 26 | berachain: { 27 | LBTC: "0x39f3294352208905fc6ebf033954E6c6455CdB4C", 28 | }, 29 | hyperliquid: { 30 | uBTC: "0x39f3294352208905fc6ebf033954E6c6455CdB4C", 31 | }, 32 | 33 | } as any; 34 | const tokenAddresses = { 35 | ethereum: { 36 | WBTC: "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599", 37 | USDC: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 38 | cbBTC: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", 39 | iBTC: "0x20157DBAbb84e3BBFE68C349d0d44E48AE7B5AD2", 40 | }, 41 | arbitrum: { 42 | WBTC: "0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f", 43 | USDC: "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", 44 | iBTC: "0x050C24dBf1eEc17babE5fc585F06116A259CC77A", 45 | }, 46 | base: { 47 | cbBTC: "0xcbB7C0000aB88B473b1f5aFd9ef808440eed33Bf", 48 | USDC: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", 49 | }, 50 | unichain: { 51 | WBTC: "0x927B51f251480a681271180DA4de28D44EC4AfB8", 52 | USDC: "0x078D782b760474a361dDA0AF3839290b0EF57AD6", 53 | }, 54 | berachain: { 55 | LBTC: "0xecAc9C5F704e954931349Da37F60E39f515c11c1", 56 | }, 57 | hyperliquid: { 58 | uBTC: "0x9FDBdA0A5e284c32744D2f17Ee5c74B284993463", 59 | }, 60 | 61 | } as any; 62 | 63 | 64 | const constructParams = (chain: Chain) => { 65 | let eventParams = [] as PartialContractEventParams[]; 66 | const contracts = contractAddresses[chain]; 67 | const tokens = tokenAddresses[chain]; 68 | 69 | Object.keys(contracts).forEach((token: string) => { 70 | const depositParams = constructTransferParams(contracts[token], true, {}, {}, chain); 71 | const withdrawParams = constructTransferParams(contracts[token], false, {}, {}, chain); 72 | eventParams.push(depositParams, withdrawParams); 73 | }); 74 | 75 | return async (fromBlock: number, toBlock: number) => { 76 | return getTxDataFromEVMEventLogs("garden", chain, fromBlock, toBlock, eventParams); 77 | }; 78 | }; 79 | 80 | const adapter: BridgeAdapter = { 81 | ethereum: constructParams("ethereum"), 82 | arbitrum: constructParams("arbitrum"), 83 | base: constructParams("base"), 84 | unichain: constructParams("unichain"), 85 | berachain: constructParams("berachain"), 86 | hyperliquid: constructParams("hyperliquid"), 87 | }; 88 | 89 | export default adapter; -------------------------------------------------------------------------------- /src/adapters/helixbox/index.ts: -------------------------------------------------------------------------------- 1 | import type { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | import type { Chain } from "@defillama/sdk/build/general"; 4 | 5 | const depositParams = (contractAddress: string): PartialContractEventParams => { 6 | return { 7 | target: contractAddress, 8 | topic: "TokenLocked((uint256,address,address,address,uint112,uint112,address,uint256),bytes32,uint112,uint112)", 9 | abi: [ 10 | "event TokenLocked((uint256 remoteChainId, address provider, address sourceToken, address targetToken, uint112 totalFee, uint112 amount, address receiver, uint256 timestamp) params, bytes32 transferId, uint112 targetAmount, uint112 fee)", 11 | ], 12 | logKeys: { 13 | blockNumber: "blockNumber", 14 | txHash: "transactionHash", 15 | }, 16 | argKeys: { 17 | token: "params.sourceToken", 18 | amount: "params.amount", 19 | to: "params.receiver", 20 | }, 21 | txKeys: { 22 | from: "from", 23 | }, 24 | isDeposit: true, 25 | }; 26 | } 27 | 28 | const withdrawalParams = (contractAddress: string): PartialContractEventParams => { 29 | return { 30 | target: contractAddress, 31 | topic: "TransferFilledExt(bytes32,(uint256,address,address,address,uint112,uint112,address,uint256))", 32 | abi: [ 33 | "event TransferFilledExt(bytes32 transferId, (uint256 remoteChainId,address provider,address sourceToken, address targetToken, uint112 sourceAmount, uint112 targetAmount, address receiver, uint256 timestamp) params)", 34 | ], 35 | logKeys: { 36 | blockNumber: "blockNumber", 37 | txHash: "transactionHash", 38 | }, 39 | txKeys: { 40 | from: "from", 41 | to: "to", 42 | }, 43 | argKeys: { 44 | token: "params.targetToken", 45 | amount: "params.targetAmount", 46 | to: "params.receiver", 47 | }, 48 | isDeposit: false, 49 | }; 50 | } 51 | 52 | const constructParams = (chain: Chain) => { 53 | let contractAddress = '0xbA5D580B18b6436411562981e02c8A9aA1776D10'; 54 | 55 | if (chain === 'blast') { 56 | contractAddress = '0xB180D7DcB5CC161C862aD60442FA37527546cAFC'; 57 | } 58 | 59 | if (chain === 'morph') { 60 | contractAddress = '0xCcD566F8dA3643A9948A1509cde0D0324D32d19b'; 61 | } 62 | 63 | const eventParams: PartialContractEventParams[] = [ 64 | depositParams(contractAddress), 65 | withdrawalParams(contractAddress), 66 | ]; 67 | 68 | return async (fromBlock: number, toBlock: number) => 69 | getTxDataFromEVMEventLogs("helixbridge", chain as Chain, fromBlock, toBlock, eventParams); 70 | } 71 | 72 | const adapter: BridgeAdapter = { 73 | arbitrum: constructParams("arbitrum"), 74 | darwinia: constructParams("darwinia"), 75 | polygon: constructParams("polygon"), 76 | bsc: constructParams("bsc"), 77 | linea: constructParams("linea"), 78 | mantle: constructParams("mantle"), 79 | scroll: constructParams("scroll"), 80 | optimism: constructParams("optimism"), 81 | gnosis: constructParams("xdai"), 82 | blast: constructParams("blast"), 83 | moonbeam: constructParams("moonbeam"), 84 | base: constructParams("base"), 85 | avalanche: constructParams("avax"), 86 | morph: constructParams("morph"), 87 | }; 88 | 89 | export default adapter; 90 | -------------------------------------------------------------------------------- /src/adapters/hop/test.ts: -------------------------------------------------------------------------------- 1 | import { getProvider } from "@defillama/sdk/build/general"; 2 | import { ethers } from "ethers"; 3 | 4 | const test = async () => { 5 | const provider = getProvider("ethereum") as any 6 | const tx = await provider.getTransaction("0x87222de8370aa4d2eb670a7fc994810313193f9a79e761f70a36ba76b9a9ce86") 7 | console.log(tx) 8 | const iface = new ethers.utils.Interface(['function bondWithdrawal(address recipient, uint256 amount, bytes32 transferNonce, uint256 bonderFee)']) 9 | console.log(iface.decodeFunctionData('bondWithdrawal', tx.data)) 10 | 11 | } 12 | 13 | test() -------------------------------------------------------------------------------- /src/adapters/hyperlane/index.ts: -------------------------------------------------------------------------------- 1 | import { setProvider, getProvider } from "@defillama/sdk"; 2 | import { LlamaProvider } from "@defillama/sdk/build/util/LlamaProvider"; 3 | import { Chain } from "@defillama/sdk/build/general"; 4 | import * as yaml from "js-yaml"; 5 | 6 | import { BridgeAdapter } from "../../helpers/bridgeAdapter.type"; 7 | 8 | const baseUri = "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/main"; 9 | const kyveApiBaseUri = "https://hyperlane.services.kyve.network"; 10 | 11 | export async function setUp(): Promise { 12 | const chains = []; 13 | 14 | const metadata = (await fetch(`${baseUri}/chains/metadata.yaml`) 15 | .then((r) => r.text()) 16 | .then((t) => yaml.load(t))) as Record; 17 | 18 | for (const [name, chainData] of Object.entries(metadata)) { 19 | if (chainData.isTestnet) continue; 20 | 21 | chains.push(name); 22 | } 23 | 24 | return chains; 25 | } 26 | 27 | interface KyveEvent { 28 | blockNumber: number; 29 | chain: string; 30 | from: string; 31 | isDeposit: boolean; 32 | timestamp: number; 33 | to: string; 34 | token: string; 35 | txHash: string; 36 | usdAmount: string; 37 | } 38 | 39 | export const getEvents = async (fromTimestamp: number, toTimestamp: number): Promise => { 40 | const apiUrl = `${kyveApiBaseUri}/events?fromTimestamp=${fromTimestamp}&toTimestamp=${toTimestamp}`; 41 | console.log(apiUrl); 42 | try { 43 | const response = await fetch(apiUrl); 44 | if (!response.ok) { 45 | console.error(`Error fetching data from Kyve API: ${response.statusText}`); 46 | return []; 47 | } 48 | const events = (await response.json()) as KyveEvent[]; 49 | 50 | const txData: any[] = events.map((event) => { 51 | let usdAmount: number | undefined = undefined; 52 | try { 53 | usdAmount = parseFloat(event.usdAmount); 54 | if (isNaN(usdAmount)) { 55 | usdAmount = undefined; 56 | } 57 | } catch { 58 | usdAmount = undefined; 59 | } 60 | 61 | return { 62 | blockNumber: event.blockNumber, 63 | chain: event.chain, 64 | from: event.from, 65 | isDeposit: event.isDeposit, 66 | timestamp: event.timestamp, 67 | to: event.to, 68 | token: event.token, 69 | txHash: event.txHash, 70 | amount: usdAmount, 71 | isUSDVolume: true, 72 | }; 73 | }); 74 | 75 | return txData; 76 | } catch (error) { 77 | console.error(`Error processing Kyve API response:`, error); 78 | return []; 79 | } 80 | }; 81 | 82 | export async function build(): Promise { 83 | const adapter: BridgeAdapter = {}; 84 | 85 | const chains = await setUp(); 86 | 87 | for (const chain of chains) { 88 | adapter[chain] = getEvents; 89 | } 90 | 91 | return adapter; 92 | } 93 | 94 | export default { isAsync: true, build }; 95 | -------------------------------------------------------------------------------- /src/adapters/hyperliquid/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 5 | 6 | const HYPERLIQUID_BRIDGE_ADDRESS = "0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7"; 7 | 8 | const erc20WithdrawalEventParams: PartialContractEventParams = constructTransferParams( 9 | HYPERLIQUID_BRIDGE_ADDRESS, 10 | false 11 | ); 12 | 13 | const erc20DepositEventParams: PartialContractEventParams = constructTransferParams(HYPERLIQUID_BRIDGE_ADDRESS, true); 14 | 15 | const constructParams = (chain: string) => { 16 | let eventParams = [] as PartialContractEventParams[]; 17 | eventParams.push(erc20WithdrawalEventParams, erc20DepositEventParams); 18 | return async (fromBlock: number, toBlock: number) => 19 | getTxDataFromEVMEventLogs("hyperliquid", chain as Chain, fromBlock, toBlock, eventParams); 20 | }; 21 | 22 | const adapter: BridgeAdapter = { 23 | arbitrum: constructParams("arbitrum"), 24 | }; 25 | 26 | export default adapter; 27 | -------------------------------------------------------------------------------- /src/adapters/ibc/errors.ts: -------------------------------------------------------------------------------- 1 | class LatestBlockNotFoundError extends Error { 2 | constructor(zoneId: string) { 3 | super(`Latest block not found for ${zoneId}`); 4 | } 5 | } -------------------------------------------------------------------------------- /src/adapters/ibc/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeNetwork } from "../../data/types"; 2 | import { BridgeAdapter } from "../../helpers/bridgeAdapter.type"; 3 | import { 4 | getBlockFromTimestamp, 5 | getIbcVolumeByZoneId, 6 | getLatestBlockForZone 7 | } from "../../helpers/mapofzones"; 8 | import bridges from "../../data/bridgeNetworkData"; 9 | 10 | const ibcBridgeNetwork = bridges.find((bridge) => bridge.bridgeDbName === "ibc"); 11 | 12 | export const getLatestBlockForZoneFromMoz = async (zoneId: string): Promise<{ 13 | number: number; 14 | timestamp: number; 15 | }> => { 16 | const block = await getLatestBlockForZone(zoneId); 17 | if (!block) { 18 | throw new LatestBlockNotFoundError(zoneId); 19 | } 20 | return { 21 | number: block.block, 22 | timestamp: block.timestamp, 23 | }; 24 | } 25 | 26 | // this returns height only 27 | export const getLatestBlockHeightForZoneFromMoz = async (zoneId: string): Promise => { 28 | const block = await getLatestBlockForZone(zoneId); 29 | if (!block) { 30 | throw new LatestBlockNotFoundError(zoneId); 31 | } 32 | return block.block; 33 | } 34 | 35 | export const findChainId = (bridgeNetwork: BridgeNetwork, chain: string) => { 36 | if (bridgeNetwork.chainMapping === undefined) { 37 | throw new Error("Chain mapping is undefined for ibc bridge network."); 38 | } 39 | 40 | if (bridgeNetwork.chainMapping[chain]) { 41 | return bridgeNetwork.chainMapping[chain]; 42 | } else if (Object.values(bridgeNetwork.chainMapping).includes(chain)) { 43 | return chain; 44 | } 45 | } 46 | 47 | export const ibcGetBlockFromTimestamp = async (bridge: BridgeNetwork, timestamp: number, chainName: string, position?: 'First' | 'Last') => { 48 | if(position === undefined) { 49 | throw new Error("Position is required for ibcGetBlockFromTimestamp"); 50 | } 51 | const chainId = findChainId(bridge, chainName); 52 | if(chainId === undefined) { 53 | throw new Error(`Could not find chain id for chain name ${chainName}`); 54 | } 55 | return await getBlockFromTimestamp(timestamp, chainId, position); 56 | } 57 | 58 | const chainExports = () => { 59 | if (ibcBridgeNetwork === undefined) { 60 | throw new Error("Could not find ibc bridge network."); 61 | } 62 | 63 | const chainNames = ibcBridgeNetwork.chains; 64 | 65 | const chainBreakdown = {} as BridgeAdapter; 66 | chainNames.forEach((chainName) => { 67 | const chainId = findChainId(ibcBridgeNetwork, chainName); 68 | if(chainId) { 69 | chainBreakdown[chainName.toLowerCase()] = getIbcVolumeByZoneId(chainId); 70 | } 71 | }); 72 | return chainBreakdown; 73 | }; 74 | 75 | const adapter: BridgeAdapter = chainExports(); 76 | 77 | export default adapter; 78 | -------------------------------------------------------------------------------- /src/adapters/intersoon/index.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | 3 | type InterSoonBridgeEvent = { 4 | block_timestamp: string; 5 | transaction_hash: string; 6 | token_transfer_from_address: string; 7 | token_transfer_to_address: string; 8 | token_address: string; 9 | token_usd_amount: string; 10 | token_amount: string; 11 | source_chain: string; 12 | destination_chain: string; 13 | }; 14 | 15 | const chains = [ 16 | "solana", 17 | "ton" 18 | ]; 19 | 20 | export const chainNameMapping: { [key: string]: string } = { 21 | "Solana Network": "solana", 22 | "Ton Network": "ton" 23 | }; 24 | 25 | export function normalizeChainName(chainName: string): string { 26 | return chainNameMapping[chainName] ?? chainName?.toLowerCase(); 27 | } 28 | 29 | 30 | export const fetchInterSoonEvents = async ( 31 | fromTimestamp: number, 32 | toTimestamp: number 33 | ): Promise => { 34 | let allResults: InterSoonBridgeEvent[] = []; 35 | let currentTimestamp = fromTimestamp; 36 | const BATCH_SIZE = 5000; 37 | 38 | while (currentTimestamp < toTimestamp) { 39 | const response = await axios.get( 40 | `https://api.ccb-relayer.soo.network/transaction/get_bridge_history?start_timestamp=${fromTimestamp}&end_timestamp=${toTimestamp}&limit=${BATCH_SIZE}` 41 | ); 42 | const { data } = response.data; 43 | 44 | if (data.length === 0) break; 45 | 46 | const normalizedBatch = data.map((row: any) => ({ 47 | ...row, 48 | source_chain: normalizeChainName(row.source_chain), 49 | destination_chain: normalizeChainName(row.destination_chain), 50 | token_usd_amount: String(row.token_usd_amount ?? 0), 51 | block_timestamp: Math.floor(row.create_timestamp / 1000), 52 | })); 53 | 54 | allResults = [...allResults, ...normalizedBatch]; 55 | console.log(`Fetched ${allResults.length} InterSoon events.`); 56 | 57 | currentTimestamp = normalizedBatch[normalizedBatch.length - 1].block_timestamp + 1; 58 | 59 | if (data.length < BATCH_SIZE) break; 60 | } 61 | 62 | return allResults; 63 | }; 64 | 65 | const adapter = chains.reduce((acc: any, chain: string) => { 66 | acc[chain] = true; 67 | return acc; 68 | }, {}); 69 | 70 | export default adapter; 71 | -------------------------------------------------------------------------------- /src/adapters/layerzero/allChains.ts: -------------------------------------------------------------------------------- 1 | const allChains = [ 2 | "Linea", 3 | "BNB Chain", 4 | "Optimism", 5 | "Gravity", 6 | "Arbitrum", 7 | "Celo Mainnet", 8 | "Aptos", 9 | "Ethereum", 10 | "Avalanche", 11 | "Polygon", 12 | "Harmony", 13 | "Fantom", 14 | "Gnosis", 15 | "Base", 16 | "Klaytn Mainnet Cypress", 17 | "zkSync Era Mainnet", 18 | "Polygon zkEVM", 19 | "Moonriver", 20 | "Moonbeam", 21 | "EBI", 22 | "opBNB Mainnet", 23 | "Core Blockchain Mainnet", 24 | "Metis", 25 | "Scroll", 26 | "Mantle", 27 | "Aurora Mainnet", 28 | "DFK", 29 | "Arbitrum Nova", 30 | "Mode", 31 | "Blast", 32 | "Sei", 33 | "Taiko", 34 | "Merit Circle", 35 | "Abstract", 36 | "Zora", 37 | "Rari Chain", 38 | "Kava", 39 | "Ink", 40 | "Orderly Mainnet", 41 | "Degen", 42 | "Lightlink", 43 | "Manta", 44 | "Viction", 45 | "Fuse Mainnet", 46 | "Iota", 47 | "Superposition", 48 | "Injective", 49 | "Ape", 50 | "Tenet", 51 | "Canto", 52 | "Conflux eSpace", 53 | "OKXChain Mainnet", 54 | "Horizen EON Mainnet", 55 | "Flare", 56 | "Loot", 57 | "TelosEVM", 58 | "Fraxtal", 59 | "Bera", 60 | "Astar", 61 | "Islander", 62 | "Zircuit", 63 | "Peaq", 64 | "Cyber", 65 | "Meter Mainnet", 66 | "Xai", 67 | "Solana", 68 | "Xchain", 69 | "Swell", 70 | "Sonic", 71 | "XPLA Mainnet", 72 | "Sanko", 73 | "Shimmer", 74 | "PGN (Public Goods Network)", 75 | "unknown", 76 | "Etherlink", 77 | "Rootstock", 78 | "Plume", 79 | "Shrapnel Subnet", 80 | "Real", 81 | "Astar zkEVM", 82 | "Flow", 83 | "Xlayer", 84 | ]; 85 | export default allChains; 86 | -------------------------------------------------------------------------------- /src/adapters/lighter/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 5 | 6 | const BRIDGE_ADDRESS = "0x3B4D794a66304F130a4Db8F2551B0070dfCf5ca7"; 7 | 8 | const erc20DepositEventParams: PartialContractEventParams = constructTransferParams(BRIDGE_ADDRESS, true); 9 | 10 | const erc20WithdrawalEventParams: PartialContractEventParams = constructTransferParams(BRIDGE_ADDRESS, false); 11 | 12 | export const lighterEventParams = [erc20DepositEventParams, erc20WithdrawalEventParams]; 13 | 14 | const constructParams = (chain: string) => { 15 | return async (fromBlock: number, toBlock: number) => { 16 | return await getTxDataFromEVMEventLogs("lighter", chain as Chain, fromBlock, toBlock, lighterEventParams); 17 | }; 18 | }; 19 | 20 | const adapter = { 21 | ethereum: constructParams("ethereum"), 22 | }; 23 | 24 | export default adapter; 25 | -------------------------------------------------------------------------------- /src/adapters/mantle/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | 4 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 5 | 6 | const ethDepositParams: PartialContractEventParams = { 7 | target: "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", 8 | topic: "ETHDepositInitiated(address,address,uint256,bytes)", 9 | abi: ["event ETHDepositInitiated(address indexed _from, address indexed _to, uint256 _amount, bytes _data)"], 10 | logKeys: { 11 | blockNumber: "blockNumber", 12 | txHash: "transactionHash", 13 | }, 14 | txKeys: { 15 | from: "from", 16 | amount: "value", 17 | }, 18 | fixedEventData: { 19 | token: WETH, 20 | to: "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", 21 | }, 22 | isDeposit: true, 23 | }; 24 | 25 | const ethWithdrawParams: PartialContractEventParams = { 26 | target: "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", 27 | topic: "ETHWithdrawalFinalized(address,address,uint256,bytes)", 28 | abi: ["event ETHWithdrawalFinalized(address indexed _from, address indexed _to, uint256 _amount,bytes _data)"], 29 | logKeys: { 30 | blockNumber: "blockNumber", 31 | txHash: "transactionHash", 32 | }, 33 | txKeys: { 34 | to: "to", 35 | amount: "value", 36 | }, 37 | fixedEventData: { 38 | token: WETH, 39 | from: "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", 40 | }, 41 | isDeposit: false, 42 | }; 43 | 44 | const ercDepositParams: PartialContractEventParams = { 45 | target: "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", 46 | topic: "ERC20DepositInitiated(address,address,address,address,uint256,bytes)", 47 | abi: [ 48 | "event ERC20DepositInitiated(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)", 49 | ], 50 | argKeys: { 51 | token: "_l1Token", 52 | amount: "_amount", 53 | from: "_from", 54 | }, 55 | logKeys: { 56 | blockNumber: "blockNumber", 57 | txHash: "transactionHash", 58 | }, 59 | 60 | fixedEventData: { 61 | to: "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", 62 | }, 63 | isDeposit: true, 64 | }; 65 | 66 | const ercWithdrawParams: PartialContractEventParams = { 67 | target: "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", 68 | topic: "ERC20WithdrawalFinalized(address,address,address,address,uint256,bytes)", 69 | abi: [ 70 | "event ERC20WithdrawalFinalized(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)", 71 | ], 72 | argKeys: { 73 | token: "_l1Token", 74 | amount: "_amount", 75 | to: "_to", 76 | }, 77 | logKeys: { 78 | blockNumber: "blockNumber", 79 | txHash: "transactionHash", 80 | }, 81 | 82 | fixedEventData: { 83 | from: "0x95fC37A27a2f68e3A647CDc081F0A89bb47c3012", 84 | }, 85 | isDeposit: false, 86 | }; 87 | 88 | const constructParams = () => { 89 | const eventParams = [ercDepositParams, ercWithdrawParams, ethDepositParams, ethWithdrawParams]; 90 | return async (fromBlock: number, toBlock: number) => 91 | getTxDataFromEVMEventLogs("mantle", "ethereum", fromBlock, toBlock, eventParams); 92 | }; 93 | 94 | const adapter: BridgeAdapter = { 95 | ethereum: constructParams(), 96 | }; 97 | 98 | export default adapter; 99 | -------------------------------------------------------------------------------- /src/adapters/meson/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 5 | 6 | const contractAddress = "0x25aB3Efd52e6470681CE037cD546Dc60726948D3"; 7 | 8 | const constructParams = (chain: string) => { 9 | let eventParams = [] as PartialContractEventParams[]; 10 | const depositParams = constructTransferParams(contractAddress, true, { 11 | excludeFrom: [contractAddress], 12 | }); 13 | const withdrawalParams = constructTransferParams(contractAddress, false, { 14 | excludeTo: [contractAddress], 15 | }); 16 | eventParams.push(depositParams, withdrawalParams); 17 | return async (fromBlock: number, toBlock: number) => 18 | getTxDataFromEVMEventLogs("meson", chain as Chain, fromBlock, toBlock, eventParams); 19 | }; 20 | 21 | const adapter: BridgeAdapter = { 22 | ethereum: constructParams("ethereum"), 23 | polygon: constructParams("polygon"), 24 | fantom: constructParams("fantom"), 25 | avalanche: constructParams("avax"), 26 | bsc: constructParams("bsc"), 27 | arbitrum: constructParams("arbitrum"), 28 | optimism: constructParams("optimism"), 29 | aurora: constructParams("aurora"), 30 | "zksync era": constructParams("era"), 31 | kava: constructParams("kava"), 32 | moonbeam: constructParams("moonbeam"), 33 | moonriver: constructParams("moonriver"), 34 | cronos: constructParams("cronos"), 35 | "polygon zkevm": constructParams("polygon_zkevm"), 36 | linea: constructParams("linea"), 37 | base: constructParams("base"), 38 | metis: constructParams("metis"), 39 | manta: constructParams("manta"), 40 | mantle: constructParams("mantle"), 41 | scroll: constructParams("scroll"), 42 | celo: constructParams("celo"), 43 | gnosis: constructParams("xdai"), 44 | merlin: constructParams("merlin"), 45 | bsquared: constructParams("b2-mainnet"), 46 | zkfair: constructParams("zkfair"), 47 | btr: constructParams("btr"), 48 | 49 | // conflux-espace 50 | // eos-evm 51 | // tron 52 | // solana 53 | // aptos 54 | // sui 55 | // SKALE Europa 56 | // SKALE Nebula 57 | // Nautilus 58 | // opbnb 59 | }; 60 | 61 | export default adapter; 62 | -------------------------------------------------------------------------------- /src/adapters/mesprotocol/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 5 | 6 | const bridgeAddresses = { 7 | ethereum: "0xbfc0e7E964F9445Aab8E3F76101FfBdEF3EDDd96", 8 | manta: "0x052848c0E8F73BBCf53001496b2C78b02efE933b", 9 | optimism: "0x052848c0E8F73BBCf53001496b2C78b02efE933b", 10 | era: "0xCD0E8Fb86fb6FC5591Bc0801490d33d515Ba613F", 11 | arbitrum: "0x052848c0E8F73BBCf53001496b2C78b02efE933b", 12 | base: "0x052848c0E8F73BBCf53001496b2C78b02efE933b", 13 | linea: "0x052848c0E8F73BBCf53001496b2C78b02efE933b", 14 | } as { [chain: string]: string }; 15 | 16 | const constructParams = (chain: string) => { 17 | let eventParams = [] as PartialContractEventParams[]; 18 | const bridgeAddress = bridgeAddresses[chain]; 19 | 20 | const depositParams = constructTransferParams(bridgeAddress, true, { 21 | excludeFrom: [bridgeAddress], 22 | }); 23 | 24 | const withdrawParams = constructTransferParams(bridgeAddress, false, { 25 | excludeTo: [bridgeAddress], 26 | }); 27 | 28 | eventParams.push(depositParams, withdrawParams); 29 | return async (fromBlock: number, toBlock: number) => 30 | getTxDataFromEVMEventLogs("mesprotocol", chain as Chain, fromBlock, toBlock, eventParams); 31 | }; 32 | 33 | const adapter: BridgeAdapter = { 34 | ethereum: constructParams("ethereum"), 35 | optimism: constructParams("optimism"), 36 | "zksync era": constructParams("era"), 37 | arbitrum: constructParams("arbitrum"), 38 | base: constructParams("base"), 39 | linea: constructParams("linea"), 40 | manta: constructParams("manta"), 41 | }; 42 | 43 | export default adapter; 44 | -------------------------------------------------------------------------------- /src/adapters/mint/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | 5 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 6 | 7 | const MINT_L1_BRIDGE = "0x2b3F201543adF73160bA42E1a5b7750024F30420"; 8 | 9 | const ercDepositParams: PartialContractEventParams = { 10 | target: MINT_L1_BRIDGE, 11 | topic: "ERC20DepositInitiated(address,address,address,address,uint256,bytes)", 12 | abi: [ 13 | "event ERC20DepositInitiated(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)", 14 | ], 15 | isDeposit: true, 16 | logKeys: { 17 | blockNumber: "blockNumber", 18 | txHash: "transactionHash", 19 | }, 20 | argKeys: { 21 | token: "_l1Token", 22 | from: "_from", 23 | amount: "_amount", 24 | }, 25 | fixedEventData: { 26 | to: MINT_L1_BRIDGE, 27 | }, 28 | }; 29 | 30 | const ethDepositParams: PartialContractEventParams = { 31 | target: MINT_L1_BRIDGE, 32 | topic: "ETHDepositInitiated(address,address,uint256,bytes)", 33 | abi: ["event ETHDepositInitiated(address indexed _from, address indexed _to, uint256 _amount, bytes _data)"], 34 | isDeposit: true, 35 | logKeys: { 36 | blockNumber: "blockNumber", 37 | txHash: "transactionHash", 38 | }, 39 | argKeys: { 40 | from: "_from", 41 | amount: "_amount", 42 | }, 43 | fixedEventData: { 44 | to: MINT_L1_BRIDGE, 45 | token: WETH, 46 | }, 47 | }; 48 | 49 | const ercWithdrawalParams: PartialContractEventParams = { 50 | target: MINT_L1_BRIDGE, 51 | topic: "ERC20WithdrawalFinalized(address,address,address,address,uint256,bytes)", 52 | abi: [ 53 | "event ERC20WithdrawalFinalized(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)", 54 | ], 55 | isDeposit: false, 56 | logKeys: { 57 | blockNumber: "blockNumber", 58 | txHash: "transactionHash", 59 | }, 60 | argKeys: { 61 | token: "_l1Token", 62 | to: "_to", 63 | amount: "_amount", 64 | }, 65 | fixedEventData: { 66 | from: MINT_L1_BRIDGE, 67 | }, 68 | }; 69 | 70 | const ethWithdrawalParams: PartialContractEventParams = { 71 | target: MINT_L1_BRIDGE, 72 | topic: "ETHWithdrawalFinalized(address,address,uint256,bytes)", 73 | abi: ["event ETHWithdrawalFinalized(address indexed _from, address indexed _to, uint256 _amount, bytes _data)"], 74 | isDeposit: false, 75 | logKeys: { 76 | blockNumber: "blockNumber", 77 | txHash: "transactionHash", 78 | }, 79 | argKeys: { 80 | to: "_to", 81 | amount: "_amount", 82 | }, 83 | fixedEventData: { 84 | from: MINT_L1_BRIDGE, 85 | token: WETH, 86 | }, 87 | }; 88 | 89 | const constructParams = () => { 90 | const eventParams = [ 91 | ercDepositParams, 92 | ethDepositParams, 93 | ercWithdrawalParams, 94 | ethWithdrawalParams, 95 | ]; 96 | return async (fromBlock: number, toBlock: number) => 97 | getTxDataFromEVMEventLogs("mint", "ethereum", fromBlock, toBlock, eventParams); 98 | }; 99 | 100 | const adapter: BridgeAdapter = { 101 | ethereum: constructParams(), 102 | }; 103 | 104 | export default adapter; 105 | -------------------------------------------------------------------------------- /src/adapters/mode/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | 5 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 6 | 7 | const MODE_L1_BRIDGE = "0x735aDBbE72226BD52e818E7181953f42E3b0FF21"; 8 | 9 | const ercDepositParams: PartialContractEventParams = { 10 | target: MODE_L1_BRIDGE, 11 | topic: "ERC20DepositInitiated(address,address,address,address,uint256,bytes)", 12 | abi: [ 13 | "event ERC20DepositInitiated(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)", 14 | ], 15 | isDeposit: true, 16 | logKeys: { 17 | blockNumber: "blockNumber", 18 | txHash: "transactionHash", 19 | }, 20 | argKeys: { 21 | token: "_l1Token", 22 | from: "_from", 23 | amount: "_amount", 24 | }, 25 | fixedEventData: { 26 | to: MODE_L1_BRIDGE, 27 | }, 28 | }; 29 | 30 | const ethDepositParams: PartialContractEventParams = { 31 | target: MODE_L1_BRIDGE, 32 | topic: "ETHDepositInitiated(address,address,uint256,bytes)", 33 | abi: ["event ETHDepositInitiated(address indexed _from, address indexed _to, uint256 _amount, bytes _data)"], 34 | isDeposit: true, 35 | logKeys: { 36 | blockNumber: "blockNumber", 37 | txHash: "transactionHash", 38 | }, 39 | argKeys: { 40 | from: "_from", 41 | amount: "_amount", 42 | }, 43 | fixedEventData: { 44 | to: MODE_L1_BRIDGE, 45 | token: WETH, 46 | }, 47 | }; 48 | 49 | const ercWithdrawalParams: PartialContractEventParams = { 50 | target: MODE_L1_BRIDGE, 51 | topic: "ERC20WithdrawalFinalized(address,address,address,address,uint256,bytes)", 52 | abi: [ 53 | "event ERC20WithdrawalFinalized(address indexed _l1Token, address indexed _l2Token, address indexed _from, address _to, uint256 _amount, bytes _data)", 54 | ], 55 | isDeposit: false, 56 | logKeys: { 57 | blockNumber: "blockNumber", 58 | txHash: "transactionHash", 59 | }, 60 | argKeys: { 61 | token: "_l1Token", 62 | to: "_to", 63 | amount: "_amount", 64 | }, 65 | fixedEventData: { 66 | from: MODE_L1_BRIDGE, 67 | }, 68 | }; 69 | 70 | const ethWithdrawalParams: PartialContractEventParams = { 71 | target: MODE_L1_BRIDGE, 72 | topic: "ETHWithdrawalFinalized(address,address,uint256,bytes)", 73 | abi: ["event ETHWithdrawalFinalized(address indexed _from, address indexed _to, uint256 _amount, bytes _data)"], 74 | isDeposit: false, 75 | logKeys: { 76 | blockNumber: "blockNumber", 77 | txHash: "transactionHash", 78 | }, 79 | argKeys: { 80 | to: "_to", 81 | amount: "_amount", 82 | }, 83 | fixedEventData: { 84 | from: MODE_L1_BRIDGE, 85 | token: WETH, 86 | }, 87 | }; 88 | 89 | const constructParams = () => { 90 | const eventParams = [ 91 | ercDepositParams, 92 | ethDepositParams, 93 | ercWithdrawalParams, 94 | ethWithdrawalParams, 95 | ]; 96 | return async (fromBlock: number, toBlock: number) => 97 | getTxDataFromEVMEventLogs("mode", "ethereum", fromBlock, toBlock, eventParams); 98 | }; 99 | 100 | const adapter: BridgeAdapter = { 101 | ethereum: constructParams(), 102 | }; 103 | 104 | export default adapter; 105 | -------------------------------------------------------------------------------- /src/adapters/movement/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | import { constructTransferParams } from "../../helpers/eventParams"; 5 | const contractAddresses: { [token: string]: string } = { 6 | MOVE: "0xf1df43a3053cd18e477233b59a25fc483c2cbe0f", 7 | USDC: "0xc209a627a7B0a19F16A963D9f7281667A2d9eFf2", 8 | USDT: "0x5e87D7e75B272fb7150B4d1a05afb6Bd71474950", 9 | WETH: "0x06E01cB086fea9C644a2C105A9F20cfC21A526e8", 10 | WBTC: "0xa55688C280E725704CFe8Ea30eD33fE5B91cE6a4", 11 | }; 12 | 13 | const constructParams = (chain: string) => { 14 | const contractAddressesArray = Object.values(contractAddresses); 15 | const depositEvents = contractAddressesArray.map((contractAddress) => constructTransferParams(contractAddress, true)); 16 | const withdrawEvents = contractAddressesArray.map((contractAddress) => 17 | constructTransferParams(contractAddress, false) 18 | ); 19 | const events = [...depositEvents, ...withdrawEvents]; 20 | 21 | return async (fromBlock: number, toBlock: number) => 22 | getTxDataFromEVMEventLogs("movement", chain as Chain, fromBlock, toBlock, events); 23 | }; 24 | 25 | const adapter: BridgeAdapter = { 26 | ethereum: constructParams("ethereum"), 27 | }; 28 | 29 | export default adapter; 30 | -------------------------------------------------------------------------------- /src/adapters/neuron/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, ContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | import { ethers } from "ethers"; 5 | 6 | const gatewayAddresses = { 7 | arbitrum: "0x2395e53b250f091f38858ca9e75398181d45682b", 8 | linea: "0x2395e53b250f091f38858ca9e75398181d45682b", 9 | optimism: "0x2395e53b250f091f38858ca9e75398181d45682b", 10 | base: "0xe204912f188514ab33ba75c96bc81fe973db1046", 11 | } as { 12 | [chain: string]: string; 13 | }; 14 | 15 | const constructParams = (chain: string) => { 16 | const chainAddress = gatewayAddresses[chain]; 17 | const depositParams: ContractEventParams = { 18 | target: null, 19 | topic: "depositNative(string)", 20 | logKeys: { 21 | blockNumber: "blockNumber", 22 | txHash: "transactionHash", 23 | to: "address", 24 | }, 25 | argKeys: { 26 | from: "user", 27 | amount: "amount", 28 | token: "token", 29 | }, 30 | abi: ["event Deposit (address indexed user, address indexed token, uint256 amount, string payload)"], 31 | topics: [ 32 | ethers.utils.hexZeroPad("0xef519b7eb82aaf6ac376a6df2d793843ebfd593de5f1a0601d3cc6ab49ebb395", 32), 33 | ], 34 | filter: { 35 | includeTxData: [{ to: chainAddress }] 36 | }, 37 | isDeposit: true, 38 | }; 39 | 40 | let eventParams = [] as any; 41 | 42 | eventParams.push(depositParams); 43 | 44 | return async (fromBlock: number, toBlock: number) => 45 | await getTxDataFromEVMEventLogs("neuron", chain as Chain, fromBlock, toBlock, eventParams); 46 | }; 47 | 48 | const adapter: BridgeAdapter = { 49 | arbitrum: constructParams("arbitrum"), 50 | linea: constructParams("linea"), 51 | optimism: constructParams("optimism"), 52 | base: constructParams("base"), 53 | }; 54 | 55 | export default adapter; 56 | -------------------------------------------------------------------------------- /src/adapters/orbiter/processTransaction.ts: -------------------------------------------------------------------------------- 1 | import { getProvider } from "@defillama/sdk/build/general"; 2 | import { BigNumber, ethers } from "ethers" 3 | import padAbi from './abi.json' assert { type: 'json' }; 4 | 5 | export async function getPadContractTxValue(chain: string, txHash: string, ): Promise<{internalValue: BigNumber, isDeposit: boolean}> { 6 | console.log(`start getPadContractTxValue, ${txHash}`); 7 | const provider = getProvider(chain); 8 | const txReceipt = await provider.getTransactionReceipt(txHash); 9 | if(!txReceipt){ 10 | return { internalValue: BigNumber.from(0), isDeposit: true }; 11 | } 12 | 13 | const contractInterface = new ethers.utils.Interface(padAbi); 14 | const parsedLogs = txReceipt.logs.map(log => { 15 | try { 16 | const parsedLog = contractInterface.parseLog({topics: log.topics as string[], data: log.data}); 17 | return parsedLog; 18 | } 19 | catch(error) { 20 | const e = error as Error; 21 | console.error("parse log fail:", e.message); 22 | return null; 23 | } 24 | }).filter(log => log != null) as ethers.utils.LogDescription[]; 25 | 26 | let totalValue = BigNumber.from(0); 27 | let isDeposit = true; 28 | for(const parsedLog of parsedLogs) { 29 | let value = BigNumber.from(0); 30 | if(parsedLog.name == 'SuccessfulLaunchMessage') { 31 | //TODO: add contract process 32 | value = parsedLog.args[6] 33 | } else if (parsedLog.name == 'SuccessfulLanding') { 34 | value = BigNumber.from(parsedLog.args[1][8]) 35 | isDeposit = false; 36 | } else if (parsedLog.name == 'SuccessfulLaunchMultiMessages') { 37 | value = parsedLog.args[6] 38 | } 39 | totalValue = totalValue.add(value); 40 | } 41 | return { internalValue: totalValue, isDeposit }; 42 | } -------------------------------------------------------------------------------- /src/adapters/pnetwork/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter } from "../../helpers/bridgeAdapter.type"; 2 | import { constructTransferParams } from "../../helpers/eventParams"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | 5 | enum Chains { 6 | ethereum = "ethereum", 7 | bsc = "bsc", 8 | } 9 | 10 | const vaultContractAddresses = { 11 | [Chains.ethereum]: ["0xe396757ec7e6ac7c8e5abe7285dde47b98f22db8", "0x112334f50cb6efcff4e35ae51a022dbe41a48135"], 12 | [Chains.bsc]: ["0x76c96b2b3cf96ee305c759a7cd985eaa67634dfc"], 13 | }; 14 | 15 | const getPeginParams = (_vault: string) => constructTransferParams(_vault, true); 16 | 17 | const getPegoutParams = (_vault: string) => constructTransferParams(_vault, false); 18 | 19 | const getParamsForVault = (_vault: string) => [getPeginParams(_vault), getPegoutParams(_vault)]; 20 | 21 | const constructParams = (chain: Chains) => { 22 | const vaults = vaultContractAddresses[chain]; 23 | const eventParams = [...vaults.map(getParamsForVault).flat()]; 24 | return async (fromBlock: number, toBlock: number) => 25 | getTxDataFromEVMEventLogs("pnetwork", chain, fromBlock, toBlock, eventParams); 26 | }; 27 | 28 | const adapter: BridgeAdapter = { 29 | bsc: constructParams(Chains.bsc), 30 | ethereum: constructParams(Chains.ethereum), 31 | }; 32 | 33 | export default adapter; 34 | -------------------------------------------------------------------------------- /src/adapters/polygon/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BridgeAdapter, 3 | ContractEventParams, 4 | PartialContractEventParams, 5 | } from "../../helpers/bridgeAdapter.type"; 6 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 7 | import { constructTransferParams } from "../../helpers/eventParams"; 8 | 9 | // 0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf is Polygon (Matic): ERC20 Bridge 10 | // 0x8484Ef722627bf18ca5Ae6BcF031c23E6e922B30 is Polygon (Matic): Ether Bridge 11 | 12 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2" 13 | 14 | const ercDepositEventParams: ContractEventParams = { 15 | target: "0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf", 16 | topic: "LockedERC20(address,address,address,uint256)", 17 | abi: [ 18 | "event LockedERC20(address indexed depositor, address indexed depositReceiver, address indexed rootToken, uint256 amount)", 19 | ], 20 | argKeys: { 21 | token: "rootToken", 22 | from: "depositor", 23 | amount: "amount", 24 | }, 25 | isDeposit: true, 26 | }; 27 | 28 | const ercWithdrawalEventParams: PartialContractEventParams = 29 | constructTransferParams( 30 | "0x40ec5B33f54e0E8A33A975908C5BA1c14e5BbbDf", 31 | false 32 | ); 33 | 34 | const etherDepositEventParams: ContractEventParams = { 35 | target: "0x8484Ef722627bf18ca5Ae6BcF031c23E6e922B30", 36 | topic: "LockedEther(address,address,uint256)", 37 | abi: [ 38 | "event LockedEther (address indexed depositor, address indexed depositReceiver, uint256 amount)", 39 | ], 40 | argKeys: { 41 | from: "depositor", 42 | amount: "amount", 43 | }, 44 | fixedEventData: { 45 | token: WETH 46 | }, 47 | isDeposit: true, 48 | }; 49 | 50 | const etherWithdrawalEventParams: ContractEventParams = { 51 | target: "0x8484Ef722627bf18ca5Ae6BcF031c23E6e922B30", 52 | topic: "ExitedEther(address,uint256)", 53 | abi: [ 54 | "event ExitedEther(address indexed exitor, uint256 amount)", 55 | ], 56 | argKeys: { 57 | to: "exitor", 58 | amount: "amount", 59 | }, 60 | fixedEventData: { 61 | token: WETH 62 | }, 63 | isDeposit: false, 64 | }; 65 | 66 | const constructParams = () => { 67 | const eventParams = [ 68 | ercDepositEventParams, 69 | ercWithdrawalEventParams, 70 | etherDepositEventParams, 71 | etherWithdrawalEventParams 72 | ]; 73 | return async (fromBlock: number, toBlock: number) => 74 | getTxDataFromEVMEventLogs("polygon", "ethereum", fromBlock, toBlock, eventParams); 75 | }; 76 | 77 | const adapter: BridgeAdapter = { 78 | ethereum: constructParams(), 79 | }; 80 | 81 | export default adapter; 82 | -------------------------------------------------------------------------------- /src/adapters/polygon_zkevm/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, ContractEventParams, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | import { ethers } from "ethers"; 4 | 5 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 6 | 7 | const BRIDGES_ADDRESS = "0x2a3DD3EB832aF982ec71669E178424b10Dca2EDe"; 8 | const etherDepositEventParams: ContractEventParams = { 9 | target: BRIDGES_ADDRESS, 10 | topic: "BridgeEvent(uint8,uint32,address,uint32,address,uint256,bytes,uint32)", 11 | abi: [ 12 | `event BridgeEvent(uint8 leafType, uint32 originNetwork, address originAddress, uint32 destinationNetwork, address destinationAddress, uint256 amount, bytes metadata, uint32 depositCount)`, 13 | ], 14 | argKeys: { 15 | from: "destinationAddress", 16 | amount: "amount", 17 | token: "originAddress", 18 | }, 19 | isDeposit: true, 20 | }; 21 | 22 | const etherWithdrawEventParams: ContractEventParams = { 23 | target: BRIDGES_ADDRESS, 24 | topic: "ClaimEvent(uint32,uint32,address,address,uint256)", 25 | abi: [ 26 | `event ClaimEvent(uint32 index, uint32 originNetwork, address originAddress, address destinationAddress, uint256 amount)`, 27 | ], 28 | argKeys: { 29 | to: "destinationAddress", 30 | amount: "amount", 31 | token: "originAddress", 32 | }, 33 | fixedEventData: { 34 | from: BRIDGES_ADDRESS, 35 | }, 36 | isDeposit: false, 37 | }; 38 | 39 | const constructParams = () => { 40 | const eventParams = [etherDepositEventParams, etherWithdrawEventParams]; 41 | return async (fromBlock: number, toBlock: number) => 42 | getTxDataFromEVMEventLogs("polygonzk", "ethereum", fromBlock, toBlock, eventParams); 43 | }; 44 | 45 | const adapter: BridgeAdapter = { 46 | ethereum: constructParams(), 47 | }; 48 | 49 | export default adapter; 50 | -------------------------------------------------------------------------------- /src/adapters/portal/README.md: -------------------------------------------------------------------------------- 1 | ## Portal Bridge Adapter 2 | 3 | The Portal bridge adapter returns `EventData` for transactions involving token locking, unlocking, minting, or burning. In the case of burning and minting, the `to` and `from` addresses are set to the Ethereum zero-address to exclude wormhole-wrapped assets from the volume calculation. However, if a wormhole-wrapped asset is burned without being transferred to its origin chain, the `to` address is set to the token bridge address to include it in the volume calculation. This is consistent with DefiLlama's methodology of not double-counting transfers in the volume calculation. 4 | -------------------------------------------------------------------------------- /src/adapters/portal/consts.ts: -------------------------------------------------------------------------------- 1 | // Wormhole: Portal core and token bridge contract addresses 2 | // https://docs.wormhole.com/wormhole/blockchain-environments/environments 3 | export const contractAddresses = { 4 | ethereum: { 5 | tokenBridge: "0x3ee18B2214AFF97000D974cf647E7C347E8fa585", 6 | coreBridge: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B", 7 | }, 8 | polygon: { 9 | tokenBridge: "0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE", 10 | coreBridge: "0x7A4B5a56256163F07b2C80A7cA55aBE66c4ec4d7", 11 | }, 12 | fantom: { 13 | tokenBridge: "0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2", 14 | coreBridge: "0x126783A6Cb203a3E35344528B26ca3a0489a1485", 15 | }, 16 | avax: { 17 | tokenBridge: "0x0e082F06FF657D94310cB8cE8B0D9a04541d8052", 18 | coreBridge: "0x54a8e5f9c4CbA08F9943965859F6c34eAF03E26c", 19 | }, 20 | bsc: { 21 | tokenBridge: "0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7", 22 | coreBridge: "0x98f3c9e6E3fAce36bAAd05FE09d375Ef1464288B", 23 | }, 24 | aurora: { 25 | tokenBridge: "0x51b5123a7b0F9b2bA265f9c4C8de7D78D52f510F", 26 | coreBridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E", 27 | }, 28 | celo: { 29 | tokenBridge: "0x796Dff6D74F3E27060B71255Fe517BFb23C93eed", 30 | coreBridge: "0xa321448d90d4e5b0A732867c18eA198e75CAC48E", 31 | }, 32 | klaytn: { 33 | tokenBridge: "0x5b08ac39EAED75c0439FC750d9FE7E1F9dD0193F", 34 | coreBridge: "0x0C21603c4f3a6387e241c0091A7EA39E43E90bb7", 35 | }, 36 | moonbeam: { 37 | tokenBridge: "0xb1731c586ca89a23809861c6103f0b96b3f57d92", 38 | coreBridge: "0xC8e2b0cD52Cf01b0Ce87d389Daa3d414d4cE29f3", 39 | }, 40 | optimism: { 41 | tokenBridge: "0x1D68124e65faFC907325e3EDbF8c4d84499DAa8b", 42 | coreBridge: "0xEe91C335eab126dF5fDB3797EA9d6aD93aeC9722", 43 | }, 44 | arbitrum: { 45 | tokenBridge: "0x0b2402144Bb366A632D14B83F244D2e0e21bD39c", 46 | coreBridge: "0xa5f208e072434bC67592E4C49C1B991BA79BCA46", 47 | }, 48 | base: { 49 | tokenBridge: "0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627", 50 | coreBridge: "0xbebdb6C8ddC678FfA9f8748f85C815C556Dd8ac6", 51 | }, 52 | solana: { 53 | tokenBridge: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb", 54 | coreBridge: "worm2ZoG2kUd4vFXhvjh93UUH596ayRfgQ2MgjNMTth", 55 | }, 56 | } as { 57 | [chain: string]: { 58 | tokenBridge: string; 59 | coreBridge: string; 60 | }; 61 | }; 62 | 63 | export const wormholeChains = Object.keys(contractAddresses).filter((chain) => chain !== "solana"); 64 | -------------------------------------------------------------------------------- /src/adapters/rainbowbridge/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, ContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | 4 | /* 5 | Rainbow Bridge is between Ethereum, Aurora, and Near. 6 | On Ethereum side, txs are simple, though I cannot figure out the destination chain. 7 | On Aurora side, there is no bridge contract or transactions, account is simply credited upon bridging (since Aurora is a smart contract). 8 | On Near side, I don't know how explorer works, its helper can make calls, but I don't know how to even view or find contracts. 9 | 10 | Just treating it like a 1-sided bridge to Aurora for now, it will only become an issue once Near is added. 11 | */ 12 | 13 | const depositEventParams: ContractEventParams = { 14 | target: "0x23Ddd3e3692d1861Ed57EDE224608875809e127f", 15 | topic: "Locked(address,address,uint256,string)", 16 | abi: ["event Locked(address indexed token, address indexed sender, uint256 amount, string accountId)"], 17 | logKeys: { 18 | blockNumber: "blockNumber", 19 | txHash: "transactionHash", 20 | }, 21 | argKeys: { 22 | token: "token", 23 | from: "sender", 24 | amount: "amount", 25 | }, 26 | fixedEventData: { 27 | to: "0x23Ddd3e3692d1861Ed57EDE224608875809e127f", 28 | }, 29 | isDeposit: true, 30 | }; 31 | 32 | const withdrawalEventParams: ContractEventParams = { 33 | target: "0x23Ddd3e3692d1861Ed57EDE224608875809e127f", 34 | topic: "Unlocked(uint128,address)", 35 | abi: ["event Unlocked(uint128 amount, address recipient)"], 36 | logKeys: { 37 | blockNumber: "blockNumber", 38 | txHash: "transactionHash", 39 | }, 40 | argKeys: { 41 | to: "recipient", 42 | amount: "amount", 43 | }, 44 | fixedEventData: { 45 | from: "0x23Ddd3e3692d1861Ed57EDE224608875809e127f", 46 | }, 47 | getTokenFromReceipt: { 48 | token: true, 49 | }, 50 | isDeposit: false, 51 | }; 52 | 53 | const constructParams = () => { 54 | const eventParams = [depositEventParams, withdrawalEventParams]; 55 | return async (fromBlock: number, toBlock: number) => 56 | getTxDataFromEVMEventLogs("rainbowbridge", "ethereum", fromBlock, toBlock, eventParams); 57 | }; 58 | 59 | const adapter: BridgeAdapter = { 60 | ethereum: constructParams(), 61 | }; 62 | 63 | export default adapter; 64 | -------------------------------------------------------------------------------- /src/adapters/rootstock-fastbtc-bridge/index.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, ethers } from "ethers"; 2 | import { BridgeAdapter, ContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | 5 | const bridgeOutFlow = "0x1a8e78b41bc5ab9ebb6996136622b9b41a601b5c"; 6 | const bridgeInFlow = "0xe43cafbdd6674df708ce9dff8762af356c2b454d"; 7 | const rbtc = "0x542fDA317318eBF1d3DEAf76E0b632741A7e677d"; 8 | 9 | const outFlowEventParams: ContractEventParams = { 10 | target: bridgeOutFlow, 11 | topic: "NewBitcoinTransfer(bytes32,string,uint256,uint256,uint256,address)", 12 | abi: ["event NewBitcoinTransfer(bytes32 indexed transferId, string btcAddress, uint256 nonce, uint256 amountSatoshi, uint256 feeSatoshi, address indexed rskAddress)"], 13 | logKeys: { 14 | blockNumber: "blockNumber", 15 | txHash: "transactionHash", 16 | }, 17 | fixedEventData: { 18 | from: bridgeOutFlow, 19 | token: rbtc, 20 | }, 21 | argKeys: { 22 | to: "btcAddress", 23 | amount: "amountSatoshi", 24 | }, 25 | isDeposit: false, 26 | }; 27 | 28 | const inFlowEventParams: ContractEventParams = { 29 | target: bridgeInFlow, 30 | topic: "NewBitcoinTransferIncoming(address,uint256,uint256,bytes32,uint256)", 31 | topics: ["0x20ef15fb02bd69f212d7a84358d8a7c05b65d25bbb920b11c4d32f837118e441"], 32 | abi: ["event NewBitcoinTransferIncoming(address indexed rskAddress, uint256 amountWei, uint256 feeWei, bytes32 btcTxHash, uint256 btcTxVout)"], 33 | logKeys: { 34 | blockNumber: "blockNumber", 35 | txHash: "transactionHash", 36 | }, 37 | fixedEventData: { 38 | from: bridgeInFlow, 39 | token: rbtc, 40 | }, 41 | argKeys: { 42 | to: "rskAddress", 43 | amount: "amountWei", 44 | }, 45 | isDeposit: true, 46 | }; 47 | 48 | const constructParams = () => { 49 | const eventParams = [outFlowEventParams, inFlowEventParams]; 50 | return async (fromBlock: number, toBlock: number) => { 51 | const logs = await getTxDataFromEVMEventLogs("fastbtc", "rsk", fromBlock, toBlock, eventParams); 52 | 53 | logs.forEach((log) => { 54 | if (!log.isDeposit) { 55 | log.amount = log?.amount?.mul(1e10) 56 | } 57 | }); 58 | 59 | return logs; 60 | }; 61 | }; 62 | 63 | const adapter: BridgeAdapter = { 64 | rootstock: constructParams(), 65 | }; 66 | 67 | export default adapter; 68 | -------------------------------------------------------------------------------- /src/adapters/rootstock-flyover/index.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, ethers } from "ethers"; 2 | import { BridgeAdapter, ContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | 5 | const flyoverContract = "0xAA9cAf1e3967600578727F975F283446A3Da6612"; 6 | const rbtc = "0x542fDA317318eBF1d3DEAf76E0b632741A7e677d"; 7 | 8 | const depositPegoutEventParams: ContractEventParams = { 9 | target: flyoverContract, 10 | topic: "PegOutDeposit(bytes32,address,uint256,uint256)", 11 | abi: ["event PegOutDeposit(bytes32 indexed quoteHash, address indexed sender, uint256 amount, uint256 timestamp)"], 12 | logKeys: { 13 | blockNumber: "blockNumber", 14 | txHash: "transactionHash", 15 | }, 16 | fixedEventData: { 17 | token: rbtc, 18 | to: flyoverContract, 19 | }, 20 | argKeys: { 21 | amount: "amount", 22 | from: "sender", 23 | }, 24 | isDeposit: false, 25 | }; 26 | 27 | const callForUserEventParams: ContractEventParams = { 28 | target: flyoverContract, 29 | topic: "CallForUser(address,address,uint256,uint256,bytes,bool,bytes32)", 30 | abi: ["event CallForUser(address indexed from, address indexed dest, uint256 gasLimit, uint256 value, bytes data, bool success, bytes32 quoteHash)"], 31 | logKeys: { 32 | blockNumber: "blockNumber", 33 | txHash: "transactionHash", 34 | }, 35 | fixedEventData: { 36 | token: rbtc, 37 | from: flyoverContract, 38 | }, 39 | argKeys: { 40 | amount: "value", 41 | to: "dest", 42 | }, 43 | isDeposit: true, 44 | }; 45 | 46 | const constructParams = () => { 47 | const eventParams = [depositPegoutEventParams, callForUserEventParams]; 48 | return async (fromBlock: number, toBlock: number) => { 49 | const logs = await getTxDataFromEVMEventLogs("flyover", "rsk", fromBlock, toBlock, eventParams); 50 | return logs; 51 | }; 52 | }; 53 | 54 | const adapter: BridgeAdapter = { 55 | rootstock: constructParams(), 56 | }; 57 | 58 | export default adapter; 59 | -------------------------------------------------------------------------------- /src/adapters/rootstock-token-bridge/constant.ts: -------------------------------------------------------------------------------- 1 | export const supportedTokens = { 2 | DAI: { 3 | address: { 4 | ethereum: "0x6B175474E89094C44Da98b954EedeAC495271d0F", 5 | rootstock: "0x6b1a73d547f4009a26b8485b63d7015d248ad406", // For Rootstock it is rDAI 6 | }, 7 | decimal: { 8 | ethereum: 18, 9 | rootstock: 18 10 | } 11 | }, 12 | USDC: { 13 | address: { 14 | ethereum: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 15 | rootstock: "0x1BDa44fda023F2af8280a16FD1b01D1A493BA6c4", // For Rootstock it is rUSDC 16 | }, 17 | decimal: { 18 | ethereum: 6, 19 | rootstock: 18 20 | } 21 | }, 22 | USDT: { 23 | address: { 24 | ethereum: "0xdAC17F958D2ee523a2206206994597C13D831ec7", 25 | rootstock: "0xef213441a85df4d7acbdae0cf78004e1e486bb96", // For Rootstock it is rUSDT 26 | }, 27 | decimal: { 28 | ethereum: 6, 29 | rootstock: 18 30 | } 31 | }, 32 | LINK: { 33 | address: { 34 | ethereum: "0x514910771AF9Ca656af840dff83E8264EcF986CA", 35 | rootstock: "0x14adae34bef7ca957ce2dde5add97ea050123827", // For Rootstock it is rLINK 36 | }, 37 | decimal: { 38 | ethereum: 18, 39 | rootstock: 18 40 | } 41 | }, 42 | BUND: { 43 | address: { 44 | ethereum: "0x8D3E855f3f55109D473735aB76F753218400fe96", 45 | rootstock: "0x4991516DF6053121121274397A8C1DAD608bc95B", // For Rootstock it is rBUND 46 | }, 47 | decimal: { 48 | ethereum: 18, 49 | rootstock: 18 50 | } 51 | }, 52 | DOC: { 53 | address: { 54 | ethereum: "0x69f6d4D4813F8e2e618DAE7572e04b6D5329E207", // For Ethereum it is eDOC 55 | rootstock: "0xE700691Da7B9851F2F35f8b8182C69C53ccad9DB", // For Rootstock it is DOC 56 | }, 57 | decimal: { 58 | ethereum: 18, 59 | rootstock: 18 60 | } 61 | }, 62 | RIF: { 63 | address: { 64 | ethereum: "0x73c08467E23F7DCB7dDBbc8d05041B74467A498A", // For Ethereum it is eRIF 65 | rootstock: "0x2aCc95758f8b5F583470bA265Eb685a8f45fC9D5", // For Rootstock it is RIF 66 | }, 67 | decimal: { 68 | ethereum: 18, 69 | rootstock: 18 70 | } 71 | }, 72 | } -------------------------------------------------------------------------------- /src/adapters/rootstock-token-bridge/index.ts: -------------------------------------------------------------------------------- 1 | import BigNumber from "bignumber.js"; 2 | import { BridgeAdapter, ContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | import { supportedTokens} from "./constant"; 5 | import { ethers } from "ethers"; 6 | 7 | const bridge = "0x9d11937e2179dc5270aa86a3f8143232d6da0e69"; 8 | 9 | const getSupportedToken = (token: string) => { 10 | if (token) { 11 | return Object.values(supportedTokens).find( st => st.address.ethereum == token || st.address.rootstock == token) 12 | } 13 | } 14 | 15 | const crossEventParams: ContractEventParams = { 16 | target: bridge, 17 | topic: "Cross(address,address,address,uint256,bytes)", 18 | abi: ["event Cross(address indexed _tokenAddress, address indexed _from, address indexed _to, uint256 _amount, bytes _userData)"], 19 | logKeys: { 20 | blockNumber: "blockNumber", 21 | txHash: "transactionHash", 22 | }, 23 | fixedEventData: { 24 | to: bridge 25 | }, 26 | argKeys: { 27 | from: "_from", 28 | amount: "_amount", 29 | }, 30 | inputDataExtraction: { 31 | inputDataABI: [ 32 | "function receiveTokensTo(address tokenToUse, address to, uint256 amount)", 33 | ], 34 | inputDataFnName: "receiveTokensTo", 35 | inputDataKeys: { 36 | token: "tokenToUse", 37 | }, 38 | }, 39 | isDeposit: false, 40 | }; 41 | 42 | 43 | const claimedEventParams: ContractEventParams = { 44 | target: bridge, 45 | topic: "Claimed(bytes32,address,address,address,uint256,bytes32,uint256,address,address,uint256)", 46 | abi: ["event Claimed(bytes32 indexed _transactionHash, address indexed _originalTokenAddress, address indexed _to, address _sender, uint256 _amount, bytes32 _blockHash, uint256 _logIndex, address _reciever, address _relayer, uint256 _fee)"], 47 | logKeys: { 48 | blockNumber: "blockNumber", 49 | txHash: "transactionHash", 50 | }, 51 | fixedEventData: { 52 | from: bridge, 53 | }, 54 | argKeys: { 55 | amount: "_amount", 56 | to: "_to", 57 | token: "_originalTokenAddress" 58 | }, 59 | isDeposit: true, 60 | }; 61 | 62 | const constructParams = () => { 63 | const eventParams = [crossEventParams, claimedEventParams]; 64 | return async (fromBlock: number, toBlock: number) => { 65 | const logs = await getTxDataFromEVMEventLogs("tokenbridge", "rsk", fromBlock, toBlock, eventParams); 66 | 67 | logs.forEach((log) => { 68 | const supportedToken = getSupportedToken(log.token) 69 | if (log.isDeposit && supportedToken) { 70 | log.token = supportedToken.address.rootstock; 71 | const decimals = supportedToken.decimal.rootstock - supportedToken.decimal.ethereum; 72 | log.amount = log.amount?.mul(ethers.BigNumber.from(10).pow(decimals)); 73 | } 74 | log.token = logs[0]?.token.toLowerCase(); 75 | }); 76 | 77 | return logs; 78 | }; 79 | }; 80 | 81 | const adapter: BridgeAdapter = { 82 | rootstock: constructParams(), 83 | }; 84 | 85 | export default adapter; -------------------------------------------------------------------------------- /src/adapters/rootstock/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, ContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | 4 | const bridge = "0x0000000000000000000000000000000001000006"; 5 | const rbtc = "0x542fDA317318eBF1d3DEAf76E0b632741A7e677d"; 6 | 7 | const peginEventParams: ContractEventParams = { 8 | target: bridge, 9 | topic: "pegin_btc(address,bytes32,int256,int256)", 10 | abi: ["event pegin_btc(address indexed receiver, bytes32 indexed btcTxHash, int256 amount, int256 protocolVersion)"], 11 | logKeys: { 12 | blockNumber: "blockNumber", 13 | txHash: "transactionHash", 14 | }, 15 | fixedEventData: { 16 | to: bridge, 17 | token: rbtc, 18 | }, 19 | argKeys: { 20 | from: "receiver", 21 | amount: "amount", 22 | }, 23 | isDeposit: true, 24 | }; 25 | 26 | const pegOutEventBeforeFingerrootsParams: ContractEventParams = { 27 | target: bridge, 28 | topic: "release_request_received(address,string,uint256)", 29 | topics: ["0x8e04e2f2c246a91202761c435d6a4971bdc7af0617f0c739d900ecd12a6d7266"], 30 | abi: ["event release_request_received(address indexed sender, bytes btcDestinationAddress, uint256 amount)"], 31 | logKeys: { 32 | blockNumber: "blockNumber", 33 | txHash: "transactionHash", 34 | }, 35 | fixedEventData: { 36 | from: bridge, 37 | token: rbtc, 38 | }, 39 | argKeys: { 40 | amount: "amount", 41 | to: "sender", 42 | }, 43 | isDeposit: false, 44 | }; 45 | 46 | const pegOutEventAfterFingerrootsParams: ContractEventParams = { 47 | target: bridge, 48 | topic: "release_request_received(address,string,uint256)", 49 | abi: ["event release_request_received(address indexed sender, string btcDestinationAddress, uint256 amount)"], 50 | logKeys: { 51 | blockNumber: "blockNumber", 52 | txHash: "transactionHash", 53 | }, 54 | fixedEventData: { 55 | from: bridge, 56 | token: rbtc, 57 | }, 58 | argKeys: { 59 | amount: "amount", 60 | to: "sender", 61 | }, 62 | isDeposit: false, 63 | }; 64 | 65 | const constructParams = () => { 66 | const eventParams = [peginEventParams, pegOutEventBeforeFingerrootsParams, pegOutEventAfterFingerrootsParams]; 67 | return async (fromBlock: number, toBlock: number) => { 68 | const logs = await getTxDataFromEVMEventLogs("rootstock", "rsk", fromBlock, toBlock, eventParams); 69 | const results = logs.map((log) => ({ ...log, amount: log?.amount?.mul(1e10) })); 70 | return results; 71 | }; 72 | }; 73 | 74 | const adapter: BridgeAdapter = { 75 | rootstock: constructParams(), 76 | }; 77 | 78 | export default adapter; 79 | -------------------------------------------------------------------------------- /src/adapters/shimmerbridge/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, ContractEventParams, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | 4 | // const WRAPPED_ERC20_ON_SHIMMER_EVM: Record = { 5 | // USDC: "0xeCE555d37C37D55a6341b80cF35ef3BC57401d1A", 6 | // USDT: "0xa4f8C7C1018b9dD3be5835bF00f335D9910aF6Bd", 7 | // WBTC: "0xb0119035d08CB5f467F9ed8Eae4E5f9626Aa7402", 8 | // ETH: "0x4638C9fb4eFFe36C49d8931BB713126063BF38f9", 9 | // AVAX: "0xEAf8553fD72417C994525178fC917882d5AEc725", 10 | // MATIC: "0xE6373A7Bb9B5a3e71D1761a6Cb4992AD8537Bf28", 11 | // BNB: "0x2A6F394085B8E33fbD9dcFc776BCE4ed95F1900D", 12 | // FTM: "0x8C96Dd1A8B1952Ce6F3a582170bb173eD591D40D", 13 | // }; 14 | 15 | const WRAPPED_TOKEN_BRIDGE_ON_SHIMMER_EVM = "0x9C6D5a71FdD306329287a835e9B8EDb7F0F17898"; 16 | 17 | const constructParams = () => { 18 | const eventParamsList = new Array(); 19 | 20 | const depositEventParams: ContractEventParams = { 21 | target: WRAPPED_TOKEN_BRIDGE_ON_SHIMMER_EVM, 22 | topic: "WrapToken(address,address,uint16,address,uint256)", 23 | abi: ["event WrapToken(address localToken, address remoteToken, uint16 remoteChainId, address to, uint256 amount)"], 24 | logKeys: { 25 | blockNumber: "blockNumber", 26 | txHash: "transactionHash", 27 | }, 28 | argKeys: { 29 | from: "to", 30 | to: "to", 31 | token: "localToken", 32 | amount: "amount", 33 | }, 34 | isDeposit: true, 35 | }; 36 | 37 | const withdrawEventParams: ContractEventParams = { 38 | target: WRAPPED_TOKEN_BRIDGE_ON_SHIMMER_EVM, 39 | topic: "UnwrapToken(address,address,uint16,address,uint256)", 40 | abi: [ 41 | "event UnwrapToken(address localToken, address remoteToken, uint16 remoteChainId, address to, uint256 amount)", 42 | ], 43 | logKeys: { 44 | blockNumber: "blockNumber", 45 | txHash: "transactionHash", 46 | }, 47 | argKeys: { 48 | from: "to", 49 | to: "to", 50 | token: "localToken", 51 | amount: "amount", 52 | }, 53 | isDeposit: false, 54 | }; 55 | 56 | eventParamsList.push(depositEventParams); 57 | eventParamsList.push(withdrawEventParams); 58 | 59 | return async (fromBlock: number, toBlock: number) => 60 | getTxDataFromEVMEventLogs("shimmerbridge", "shimmer_evm", fromBlock, toBlock, eventParamsList); 61 | }; 62 | 63 | // On ShimmerBridge, ShimmerEVM is the only destination chain from other EVM chains like Ethereum, Polygon and etc 64 | const adapter: BridgeAdapter = { 65 | shimmer_evm: constructParams(), 66 | }; 67 | 68 | export default adapter; 69 | -------------------------------------------------------------------------------- /src/adapters/squid/constants.ts: -------------------------------------------------------------------------------- 1 | export const squidRouterAddresses = { 2 | default: "0xce16F69375520ab01377ce7B88f5BA8C48F8D666", 3 | blast: "0x492751eC3c57141deb205eC2da8bFcb410738630", 4 | fraxtal: "0xDC3D8e1Abe590BCa428a8a2FC4CfDbD1AcF57Bd9", 5 | }; 6 | 7 | export const axelarGatewayAddresses = { 8 | ethereum: "0x4F4495243837681061C4743b74B3eEdf548D56A5", 9 | bsc: "0x304acf330bbE08d1e512eefaa92F6a57871fD895", 10 | polygon: "0x6f015F16De9fC8791b234eF68D486d2bF203FBA8", 11 | avax: "0x5029C0EFf6C34351a0CEc334542cDb22c7928f78", 12 | fantom: "0x304acf330bbE08d1e512eefaa92F6a57871fD895", 13 | arbitrum: "0xe432150cce91c13a887f7D836923d5597adD8E31", 14 | base: "0xe432150cce91c13a887f7d836923d5597add8e31", 15 | linea: "0xe432150cce91c13a887f7d836923d5597add8e31", 16 | celo: "0xe432150cce91c13a887f7d836923d5597add8e31", 17 | moonbeam: "0x4F4495243837681061C4743b74B3eEdf548D56A5", 18 | kava: "0xe432150cce91c13a887f7D836923d5597adD8E31", 19 | filecoin: "0xe432150cce91c13a887f7D836923d5597adD8E31", 20 | optimism: "0xe432150cce91c13a887f7D836923d5597adD8E31", 21 | mantle: "0xe432150cce91c13a887f7D836923d5597adD8E31", 22 | scroll: "0xe432150cce91c13a887f7D836923d5597adD8E31", 23 | blast: "0xe432150cce91c13a887f7D836923d5597adD8E31", 24 | fraxtal: "0xe432150cce91c13a887f7D836923d5597adD8E31", 25 | immutable: "0xe432150cce91c13a887f7D836923d5597adD8E31", 26 | } as { 27 | [chain: string]: string; 28 | }; 29 | 30 | export const coralSpokeAddresses = { 31 | default: "0xA4cE01bD7Dd91DA968a7C4A8D04282a3f5eA06bB", 32 | new: "0xdf4fFDa22270c12d0b5b3788F1669D709476111E" 33 | } as { 34 | [chain: string]: string; 35 | }; 36 | 37 | export const stablecoins = { 38 | USDC: { 39 | ethereum: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", 40 | polygon: "0x2791bca1f2de4661ed88a30c99a7a9449aa84174", 41 | arbitrum: "0xff970a61a04b1ca14834a43f5de4533ebddb5cc8", 42 | bsc: "0x8ac76a51cc950d9822d68b83fe1ad97b32cd580d", 43 | base: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", 44 | avalanche: "0xb97ef9ef8734c71904d8002f8b6bc66dd9c48a6e", 45 | celo: "0xceba9300f2b948710d2653dd7b07f33a8b32118c", 46 | optimism: "0x0b2c639c533813f4aa9d7837caf62653d097ff85", 47 | }, 48 | USDT: { 49 | ethereum: "0xdac17f958d2ee523a2206206994597c13d831ec7", 50 | polygon: "0xc2132d05d31c914a87c6611c10748aeb04b58e8f", 51 | arbitrum: "0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9", 52 | bsc: "0x55d398326f99059ff775485246999027b3197955", 53 | avalanche: "0x9702230a8ea53601f5cd2dc00fdbc13d4df4a8c7", 54 | celo: "0x48065fbbe25f71c9282ddf5e1cd6d6a887483d5e", 55 | optimism: "0x94b008aa00579c1307b0ef2c499ad98a8ce58e58", 56 | linea: "0xa219439258ca9da29e9cc4ce5596924745e12b93", 57 | 58 | } 59 | }; 60 | 61 | export const chainIdToName: { [key: string]: string } = { 62 | "0x1": "ethereum", 63 | "0x38": "bsc", 64 | "0x89": "polygon", 65 | "0xa4b1": "arbitrum", 66 | "0xa": "optimism", 67 | "0x2105": "base", 68 | "0xe708": "linea", 69 | "0x504": "moonbeam", 70 | "0xa86a": "avalanche", 71 | "0xfa": "fantom", 72 | "0xa4ec": "celo", 73 | // Add other chains as needed 74 | }; 75 | 76 | export const chainNameMapping: { [key: string]: string } = { 77 | avax: "avalanche", 78 | ethereum: "ethereum", 79 | polygon: "polygon", 80 | arbitrum: "arbitrum", 81 | optimism: "optimism", 82 | base: "base", 83 | linea: "linea", 84 | moonbeam: "moonbeam", 85 | bsc: "bsc", 86 | fantom: "fantom", 87 | blast: "blast", 88 | fraxtal: "fraxtal" 89 | }; 90 | -------------------------------------------------------------------------------- /src/adapters/suibridge/index.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber, ethers } from "ethers"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | import { getProvider } from "@defillama/sdk"; 5 | 6 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 7 | const WBTC = "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599"; 8 | const USDT = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; 9 | const SUI_BRIDGE = "0xda3bD1fE1973470312db04551B65f401Bc8a92fD"; 10 | const SUI_BRIDGE_CONFIG = "0x72D34Fe82c71Bf8120647518e5128e53106a1540"; 11 | 12 | const erc20DepositParams: PartialContractEventParams = { 13 | target: SUI_BRIDGE, 14 | topic: "TokensDeposited(uint8,uint64,uint8,uint8,uint64,address,bytes)", 15 | abi: [ 16 | "event TokensDeposited(uint8 indexed sourceChainID, uint64 indexed nonce, uint8 indexed destinationChainID, uint8 tokenID, uint64 suiAdjustedAmount, address senderAddress, bytes recipientAddress)", 17 | ], 18 | logKeys: { 19 | blockNumber: "blockNumber", 20 | txHash: "transactionHash", 21 | }, 22 | argKeys: { 23 | from: "senderAddress", 24 | to: "recipientAddress", 25 | amount: "suiAdjustedAmount", 26 | token: "tokenID", 27 | }, 28 | argGetters: { 29 | token: (log) => getTokenAddressFromTokenID(log.tokenID), 30 | }, 31 | fixedEventData: { 32 | to: SUI_BRIDGE, 33 | }, 34 | isDeposit: true, 35 | }; 36 | 37 | const erc20WithdrawParams: PartialContractEventParams = { 38 | target: SUI_BRIDGE, 39 | topic: "TokensClaimed(uint8,uint64,uint8,uint8,uint256,bytes,address)", 40 | abi: [ 41 | "event TokensClaimed(uint8 indexed sourceChainID, uint64 indexed nonce, uint8 indexed destinationChainID, uint8 tokenID, uint256 erc20AdjustedAmount, bytes senderAddress, address recipientAddress)", 42 | ], 43 | logKeys: { 44 | blockNumber: "blockNumber", 45 | txHash: "transactionHash", 46 | }, 47 | argKeys: { 48 | from: "senderAddress", 49 | to: "recipientAddress", 50 | amount: "erc20AdjustedAmount", 51 | token: "tokenID" 52 | }, 53 | argGetters: { 54 | token: (log) => getTokenAddressFromTokenID(log.tokenID), 55 | }, 56 | fixedEventData: { 57 | from: SUI_BRIDGE, 58 | }, 59 | isDeposit: false, 60 | }; 61 | 62 | const constructParams = () => { 63 | const eventParams = [erc20WithdrawParams, erc20DepositParams]; 64 | return async (fromBlock: number, toBlock: number) => { 65 | const eventLogsRes = await getTxDataFromEVMEventLogs("suibridge", "ethereum", fromBlock, toBlock, eventParams); 66 | return eventLogsRes.map((event) => { 67 | if (event.isDeposit) { 68 | let erc20TokenAmount = formatSuiAmount(event.amount, event.token); 69 | event.amount = erc20TokenAmount; 70 | } 71 | return event; 72 | }); 73 | }; 74 | }; 75 | 76 | // TODO: use config contract read to get the token address from tokenID 77 | const getTokenAddressFromTokenID = (tokenID: number) => { 78 | switch (tokenID) { 79 | case 2: 80 | return WETH; 81 | case 3: 82 | return WBTC; 83 | case 4: 84 | return USDT; 85 | default: 86 | return ""; 87 | } 88 | } 89 | 90 | // TODO: get decimal values from on chain contract call 91 | const formatSuiAmount = (amount: BigNumber, token: string) => { 92 | switch (token) { 93 | case WETH: 94 | return amount.mul(10**10); 95 | case WBTC: 96 | return amount.mul(10**10); 97 | case USDT: 98 | return amount.mul(10**12); 99 | default: 100 | return amount; 101 | } 102 | } 103 | 104 | const adapter: BridgeAdapter = { 105 | ethereum: constructParams(), 106 | }; 107 | 108 | export default adapter; 109 | -------------------------------------------------------------------------------- /src/adapters/symbiosis/constants.ts: -------------------------------------------------------------------------------- 1 | export enum ChainId { 2 | ETH_MAINNET = 1, 3 | BSC_MAINNET = 56, 4 | MATIC_MAINNET = 137, 5 | AVAX_MAINNET = 43114, 6 | BOBA_MAINNET = 288, 7 | BOBA_BNB = 56288, 8 | TELOS_MAINNET = 40, 9 | KAVA_MAINNET = 2222, 10 | ZKSYNC_MAINNET = 324, 11 | ARBITRUM_MAINNET = 42161, 12 | ARBITRUM_NOVA = 42170, 13 | OPTIMISM_MAINNET = 10, 14 | POLYGON_ZK = 1101, 15 | TRON_MAINNET = 728126428, 16 | LINEA_MAINNET = 59144, 17 | MANTLE_MAINNET = 5000, 18 | BASE_MAINNET = 8453, 19 | SCROLL_MAINNET = 534352, 20 | MANTA_MAINNET = 169, 21 | METIS_MAINNET = 1088, 22 | BAHAMUT_MAINNET = 5165, 23 | MODE_MAINNET = 34443, 24 | RSK_MAINNET = 30, 25 | BLAST_MAINNET = 81457, 26 | MERLIN_MAINNET = 4200, 27 | ZKLINK_MAINNET = 810180, 28 | CORE_MAINNET = 1116, 29 | TAIKO_MAINNET = 167000, 30 | SEI_EVM_MAINNET = 1329, 31 | ZETACHAIN_MAINNET = 7000, 32 | CRONOS_MAINNET = 25, 33 | FRAXTAL_MAINNET = 252, 34 | GRAVITY_MAINNET = 1625, 35 | BSQUARED_MAINNET = 223, 36 | CRONOS_ZK_MAINNET = 388, 37 | MORPH_MAINNET = 2818, 38 | GOAT_MAINNET = 2345, 39 | SONIC_MAINNET = 146, 40 | ABSTRACT_MAINNET = 2741, 41 | GNOSIS_MAINNET = 100, 42 | BERACHAIN_MAINNET = 80094, 43 | 44 | } 45 | export const AddressZero = "0x0000000000000000000000000000000000000000" 46 | 47 | export const CHAINS_MAP: Record = { 48 | [ChainId.ETH_MAINNET]: 'ethereum', 49 | [ChainId.BSC_MAINNET]: 'bsc', 50 | [ChainId.AVAX_MAINNET]: 'avax', 51 | [ChainId.MATIC_MAINNET]: 'polygon', 52 | [ChainId.TELOS_MAINNET]: 'telos', 53 | [ChainId.KAVA_MAINNET]: 'kava', 54 | [ChainId.BOBA_MAINNET]: 'boba', 55 | [ChainId.BOBA_BNB]: 'boba_bnb', 56 | [ChainId.ZKSYNC_MAINNET]: 'era', 57 | [ChainId.ARBITRUM_MAINNET]: 'arbitrum', 58 | [ChainId.OPTIMISM_MAINNET]: 'optimism', 59 | [ChainId.ARBITRUM_NOVA]: 'arbitrum_nova', 60 | [ChainId.POLYGON_ZK]: 'polygon_zkevm', 61 | [ChainId.LINEA_MAINNET]: 'linea', 62 | [ChainId.MANTLE_MAINNET]: 'mantle', 63 | [ChainId.BASE_MAINNET]: 'base', 64 | [ChainId.TRON_MAINNET]: 'tron', 65 | [ChainId.SCROLL_MAINNET]: 'scroll', 66 | [ChainId.MANTA_MAINNET]: 'manta', 67 | [ChainId.METIS_MAINNET]: 'metis', 68 | [ChainId.MODE_MAINNET]: 'mode', 69 | [ChainId.BAHAMUT_MAINNET]: 'bahamut', 70 | [ChainId.RSK_MAINNET]: 'rsk', 71 | [ChainId.BLAST_MAINNET]: 'blast', 72 | [ChainId.MERLIN_MAINNET]: 'merlin', 73 | [ChainId.ZKLINK_MAINNET]: 'zklink', 74 | [ChainId.CORE_MAINNET]: 'core', 75 | [ChainId.TAIKO_MAINNET]: 'taiko', 76 | [ChainId.SEI_EVM_MAINNET]: 'sei_v2', // TODO no chain on defillama bridges 77 | [ChainId.ZETACHAIN_MAINNET]: 'zeta', 78 | [ChainId.CRONOS_MAINNET]: 'cronos', 79 | [ChainId.FRAXTAL_MAINNET]: 'fraxtal', 80 | [ChainId.GRAVITY_MAINNET]: 'gravity', 81 | [ChainId.BSQUARED_MAINNET]: 'bsquared', 82 | [ChainId.CRONOS_ZK_MAINNET]: 'cronos_zkevm', 83 | [ChainId.MORPH_MAINNET]: 'morph', 84 | [ChainId.GOAT_MAINNET]: 'goat', // TODO no chain on defillama bridges 85 | [ChainId.SONIC_MAINNET]: 'sonic', 86 | [ChainId.ABSTRACT_MAINNET]: 'abstract', // TODO no chain on defillama bridges 87 | [ChainId.GNOSIS_MAINNET]: 'xdai', 88 | [ChainId.BERACHAIN_MAINNET]: 'berachain', // TODO no chain on defillama bridges 89 | } 90 | 91 | export const CHAIN_ADAPTER_MAP: Record = { 92 | 'avax': 'avalanche', 93 | 'boba_bnb': 'boba bnb', 94 | 'era': 'zksync era', 95 | 'arbitrum_nova': 'arbitrum nova', 96 | 'polygon_zkevm': 'polygon zkevm', 97 | 'rsk': 'rootstock', 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/adapters/symbiosis/events.ts: -------------------------------------------------------------------------------- 1 | import {PartialContractEventParams} from "../../helpers/bridgeAdapter.type"; 2 | 3 | type Events = { 4 | synthesizeRequestParams: PartialContractEventParams 5 | synthesizeCompletedParams: PartialContractEventParams 6 | burnRequestParams: PartialContractEventParams 7 | burnCompletedParams: PartialContractEventParams 8 | } 9 | export const events: Events = { 10 | synthesizeRequestParams: { 11 | target: "", 12 | topic: "SynthesizeRequest(bytes32,address,uint256,address,address,uint256,address)", 13 | abi: [ 14 | "event SynthesizeRequest(bytes32 id,address indexed from,uint256 indexed chainID,address indexed revertableAddress,address to,uint256 amount,address token)", 15 | ], 16 | logKeys: { 17 | blockNumber: "blockNumber", 18 | txHash: "transactionHash", 19 | }, 20 | argKeys: { 21 | amount: "amount", 22 | to: "to", 23 | from: "from", 24 | token: "token", 25 | }, 26 | isDeposit: true, 27 | }, 28 | synthesizeCompletedParams: { 29 | target: "", 30 | topic: "SynthesizeCompleted(bytes32,address,bytes32,uint256,uint256,address)", 31 | abi: [ 32 | "event SynthesizeCompleted(bytes32 indexed id,address indexed to,bytes32 indexed crossChainID,uint256 amount,uint256 bridgingFee,address token)", 33 | ], 34 | logKeys: { 35 | blockNumber: "blockNumber", 36 | txHash: "transactionHash", 37 | }, 38 | argKeys: { 39 | amount: "amount", 40 | to: "to", 41 | from: "to", 42 | token: "token", 43 | }, 44 | isDeposit: true, 45 | }, 46 | burnRequestParams: { 47 | target: "", 48 | topic: "BurnRequest(bytes32,address,uint256,address,address,uint256,address)", 49 | abi: [ 50 | "event BurnRequest(bytes32 id,address indexed from,uint256 indexed chainID,address indexed revertableAddress,address to,uint256 amount,address token)", 51 | ], 52 | logKeys: { 53 | blockNumber: "blockNumber", 54 | txHash: "transactionHash", 55 | }, 56 | argKeys: { 57 | amount: "amount", 58 | to: "to", 59 | from: "from", 60 | token: "token", 61 | }, 62 | isDeposit: false, 63 | }, 64 | burnCompletedParams: { 65 | target: "", 66 | topic: "BurnCompleted(bytes32,bytes32,address,uint256,uint256,address)", 67 | abi: [ 68 | "event BurnCompleted(bytes32 indexed id,bytes32 indexed crossChainID,address indexed to,uint256 amount,uint256 bridgingFee,address token)", 69 | ], 70 | logKeys: { 71 | blockNumber: "blockNumber", 72 | txHash: "transactionHash", 73 | }, 74 | argKeys: { 75 | amount: "amount", 76 | to: "to", 77 | from: "to", 78 | token: "token", 79 | }, 80 | isDeposit: false, 81 | }, 82 | } -------------------------------------------------------------------------------- /src/adapters/symbiosis/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | import { events } from "./events"; 5 | import { contracts } from "./contracts"; 6 | import { AddressZero, CHAIN_ADAPTER_MAP, CHAINS_MAP } from "./constants"; 7 | 8 | const buildAdapter = (): BridgeAdapter => { 9 | const eventParams: PartialContractEventParams[] = []; 10 | 11 | const adapter: BridgeAdapter = {} 12 | 13 | contracts.forEach(({ chainId, portal, synthesis }) => { 14 | if (portal !== AddressZero) { 15 | eventParams.push({ 16 | ...events.synthesizeRequestParams, 17 | target: portal, 18 | }); 19 | eventParams.push({ 20 | ...events.burnCompletedParams, 21 | target: portal, 22 | }); 23 | } 24 | 25 | if (synthesis !== AddressZero) { 26 | eventParams.push({ 27 | ...events.burnRequestParams, 28 | target: synthesis, 29 | }); 30 | eventParams.push({ 31 | ...events.synthesizeCompletedParams, 32 | target: synthesis, 33 | }); 34 | } 35 | 36 | const chainKey = CHAINS_MAP[chainId] 37 | const adapterKey = CHAIN_ADAPTER_MAP[chainKey] || chainKey 38 | adapter[adapterKey] = async (fromBlock: number, toBlock: number) => { 39 | return getTxDataFromEVMEventLogs("symbiosis", chainKey as Chain, fromBlock, toBlock, eventParams); 40 | } 41 | }) 42 | 43 | return adapter 44 | }; 45 | 46 | const adapter: BridgeAdapter = buildAdapter(); 47 | 48 | export default adapter; 49 | -------------------------------------------------------------------------------- /src/adapters/threshold-network/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, ContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | import { EventData } from "../../utils/types"; 4 | 5 | const bridge = "0x5e4861a80B55f035D899f66772117F00FA0E8e7B"; 6 | const tbtc = "0x18084fbA666a33d37592fA2633fD49a74DD93a88"; 7 | const tbtcVault = "0x9c070027cdc9dc8f82416b2e5314e11dfb4fe3cd"; 8 | 9 | export type ExtendedContractEventParams = ContractEventParams & { 10 | extraData?: { [key: string]: any }; 11 | }; 12 | 13 | const mintedEventParams: ContractEventParams = { 14 | target: tbtcVault, 15 | topic: "Minted(address,uint256)", 16 | abi: ["event Minted(address indexed to, uint256 amount)"], 17 | logKeys: { 18 | blockNumber: "blockNumber", 19 | txHash: "transactionHash", 20 | }, 21 | fixedEventData: { 22 | token: tbtc, 23 | from: bridge, 24 | }, 25 | argKeys: { 26 | to: "to", 27 | amount: "amount", 28 | }, 29 | isDeposit: true, 30 | }; 31 | 32 | const redeemEventParams: ContractEventParams = { 33 | target: tbtcVault, 34 | topic: "Unminted(address,uint256)", 35 | abi: ["event Unminted(address indexed from, uint256 amount)"], 36 | logKeys: { 37 | blockNumber: "blockNumber", 38 | txHash: "transactionHash", 39 | }, 40 | fixedEventData: { 41 | token: tbtc, 42 | to: bridge, 43 | }, 44 | argKeys: { 45 | from: "from", 46 | amount: "amount", 47 | }, 48 | isDeposit: false, 49 | }; 50 | 51 | const constructParams = (chain: string) => { 52 | const eventParams = [mintedEventParams, redeemEventParams]; 53 | return async (fromBlock: number, toBlock: number) => { 54 | const evmEventLogs = await getTxDataFromEVMEventLogs("thresholdnetwork", chain, fromBlock, toBlock, eventParams); 55 | 56 | const mintAndRedeemEventLogs: EventData[] = []; 57 | evmEventLogs.forEach((eventLog) => { 58 | if (eventLog.isDeposit) { 59 | !eventLog.amount.eq(0) && mintAndRedeemEventLogs.push(eventLog); 60 | } else { 61 | mintAndRedeemEventLogs.push(eventLog); 62 | } 63 | }); 64 | 65 | return mintAndRedeemEventLogs; 66 | }; 67 | }; 68 | 69 | const adapter: BridgeAdapter = { 70 | ethereum: constructParams("ethereum"), 71 | }; 72 | 73 | export default adapter; 74 | -------------------------------------------------------------------------------- /src/adapters/usdt0/index.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 3 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 4 | 5 | type Address = `0x${string}`; 6 | 7 | interface Deployment { 8 | oapp: Address; 9 | token: Address; 10 | } 11 | 12 | const deployments = { 13 | arbitrum: [{ 14 | oapp: "0x14E4A1B13bf7F943c8ff7C51fb60FA964A298D92", 15 | token: "0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", 16 | }], 17 | berachain: [{ 18 | oapp: "0x3Dc96399109df5ceb2C226664A086140bD0379cB", 19 | token: "0x779Ded0c9e1022225f8E0630b35a9b54bE713736", 20 | }], 21 | corn: [{ 22 | oapp: "0x3f82943338a8a76c35BFA0c1828aA27fd43a34E4", 23 | token: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb", 24 | }], 25 | ethereum: [{ 26 | oapp: "0x6C96dE32CEa08842dcc4058c14d3aaAD7Fa41dee", 27 | token: "0xdAC17F958D2ee523a2206206994597C13D831ec7", // USDT 28 | }], 29 | flare: [{ 30 | oapp: "0x567287d2A9829215a37e3B88843d32f9221E7588", 31 | token: "0xe7cd86e13AC4309349F30B3435a9d337750fC82D", 32 | }], 33 | hyperliquid: [{ 34 | oapp: "0x904861a24F30EC96ea7CFC3bE9EA4B476d237e98", 35 | token: "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb", 36 | }], 37 | ink: [{ 38 | oapp: "0x1cB6De532588fCA4a21B7209DE7C456AF8434A65", 39 | token: "0x0200C29006150606B650577BBE7B6248F58470c1", 40 | }], 41 | optimism: [{ 42 | oapp: "0xF03b4d9AC1D5d1E7c4cEf54C2A313b9fe051A0aD", 43 | token: "0x01bFF41798a0BcF287b996046Ca68b395DbC1071", 44 | }], 45 | sei: [{ 46 | oapp: "0x56Fe74A2e3b484b921c447357203431a3485CC60", 47 | token: "0x9151434b16b9763660705744891fA906F660EcC5", 48 | }], 49 | unichain: [{ 50 | oapp: "0xc07bE8994D035631c36fb4a89C918CeFB2f03EC3", 51 | token: "0x9151434b16b9763660705744891fA906F660EcC5", 52 | }], 53 | } satisfies Record; 54 | 55 | const depositParams = { 56 | topic: "OFTSent(bytes32,uint32,address,uint256,uint256)", 57 | abi: [ 58 | "event OFTSent(bytes32 indexed guid, uint32 dstEid, address indexed fromAddress, uint256 amountSentLD, uint256 amountReceivedLD)", 59 | ], 60 | logKeys: { 61 | blockNumber: "blockNumber", 62 | txHash: "transactionHash", 63 | }, 64 | argKeys: { 65 | from: "fromAddress", 66 | amount: "amountSentLD", 67 | }, 68 | isDeposit: true, 69 | } satisfies Partial; 70 | 71 | const withdrawalParams = { 72 | topic: "OFTReceived(bytes32,uint32,address,uint256)", 73 | abi: ["event OFTReceived(bytes32 indexed guid, uint32 srcEid, address indexed toAddress, uint256 amountReceivedLD)"], 74 | logKeys: { 75 | blockNumber: "blockNumber", 76 | txHash: "transactionHash", 77 | }, 78 | argKeys: { 79 | to: "toAddress", 80 | amount: "amountReceivedLD", 81 | }, 82 | isDeposit: false, 83 | } satisfies Partial; 84 | 85 | const constructParams = (chain: keyof typeof deployments) => { 86 | const eventParams = deployments[chain].flatMap(deployment => [ 87 | { 88 | ...depositParams, 89 | target: deployment.oapp, 90 | fixedEventData: { 91 | token: deployment.token, 92 | to: deployment.token, 93 | }, 94 | }, 95 | { 96 | ...withdrawalParams, 97 | target: deployment.oapp, 98 | fixedEventData: { 99 | token: deployment.token, 100 | from: deployment.token, 101 | }, 102 | }, 103 | ]); 104 | 105 | return async (fromBlock: number, toBlock: number) => 106 | getTxDataFromEVMEventLogs("usdt0", chain, fromBlock, toBlock, eventParams); 107 | } 108 | 109 | const adapter: BridgeAdapter = Object.fromEntries((Object.keys(deployments) as Array).map(chain => [chain, constructParams(chain)])); 110 | 111 | export default adapter; -------------------------------------------------------------------------------- /src/adapters/xswap/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | import { Chain } from "@defillama/sdk/build/general"; 4 | 5 | const routerAddress = "0xe1c14b9f065dead2e89ee35382f8bd42bdb87a04"; 6 | 7 | const nativeTokens: Record = { 8 | ethereum: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", 9 | arbitrum: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", 10 | optimism: "0x4200000000000000000000000000000000000006", 11 | base: "0x4200000000000000000000000000000000000006", 12 | polygon: "0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270", 13 | avax: "0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7", 14 | }; 15 | 16 | const constructParams = (chain: string) => { 17 | let eventParams = [] as any; 18 | const transferWithdrawalParams: PartialContractEventParams = { 19 | target: routerAddress, 20 | topic: "MessageSent(bytes32,uint64,address,bytes,address,uint256,uint256,address,uint256)", 21 | abi: [ 22 | "event MessageSent(bytes32 indexed messageId, uint64 indexed destinationChainSelector, address indexed sender, bytes data, address token, uint256 tokenAmount, uint256 valueForInstantCcipRecieve, address transferedToken, uint256 transferedTokenAmount)", 23 | ], 24 | logKeys: { 25 | blockNumber: "blockNumber", 26 | txHash: "transactionHash", 27 | }, 28 | argKeys: { 29 | from: "sender", 30 | token: "token", 31 | amount: "tokenAmount", 32 | }, 33 | 34 | fixedEventData: { 35 | to: routerAddress, 36 | }, 37 | isDeposit: true, 38 | }; 39 | const transferDepositParams: PartialContractEventParams = { 40 | target: routerAddress, 41 | topic: "MessageReceived(bytes32,uint64,address,bytes,address,uint256)", 42 | abi: [ 43 | "event MessageReceived(bytes32 indexed messageId, uint64 indexed sourceChainSelector, address indexed sender, bytes data, address token, uint256 tokenAmount)", 44 | ], 45 | logKeys: { 46 | blockNumber: "blockNumber", 47 | txHash: "transactionHash", 48 | }, 49 | argKeys: { 50 | to: "sender", 51 | token: "token", 52 | amount: "tokenAmount", 53 | }, 54 | fixedEventData: { 55 | from: routerAddress, 56 | }, 57 | isDeposit: false, 58 | }; 59 | eventParams.push(transferWithdrawalParams, transferDepositParams); 60 | 61 | return async (fromBlock: number, toBlock: number) => { 62 | const eventLogData = await getTxDataFromEVMEventLogs("xswap", chain as Chain, fromBlock, toBlock, eventParams); 63 | 64 | return eventLogData.map((event) => { 65 | if (event.token === "0x0000000000000000000000000000000000000000") { 66 | event.token = nativeTokens[chain]; 67 | } 68 | return event; 69 | }); 70 | }; 71 | }; 72 | 73 | const adapter: BridgeAdapter = { 74 | ethereum: constructParams("ethereum"), 75 | polygon: constructParams("polygon"), 76 | avalanche: constructParams("avax"), 77 | arbitrum: constructParams("arbitrum"), 78 | base: constructParams("base"), 79 | optimism: constructParams("optimism"), 80 | }; 81 | 82 | export default adapter; 83 | -------------------------------------------------------------------------------- /src/adapters/zircuit/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | 4 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 5 | const L1StandardBridge = "0x386B76D9cA5F5Fb150B6BFB35CF5379B22B26dd8"; 6 | 7 | const ethDepositParams: PartialContractEventParams = { 8 | target: L1StandardBridge, 9 | topic: "ETHBridgeInitiated(address,address,uint256,bytes)", 10 | abi: ["event ETHBridgeInitiated(address indexed from, address indexed to, uint256 amount, bytes extraData)"], 11 | logKeys: { 12 | blockNumber: "blockNumber", 13 | txHash: "transactionHash", 14 | }, 15 | txKeys: { 16 | from: "from", 17 | amount: "value", 18 | }, 19 | fixedEventData: { 20 | token: WETH, 21 | to: L1StandardBridge, 22 | }, 23 | isDeposit: true, 24 | }; 25 | 26 | const ethWithdrawParams: PartialContractEventParams = { 27 | target: L1StandardBridge, 28 | topic: "ETHBridgeFinalized(address,address,uint256,bytes)", 29 | abi: ["event ETHBridgeFinalized(address indexed from, address indexed to, uint256 amount, bytes extraData)"], 30 | logKeys: { 31 | blockNumber: "blockNumber", 32 | txHash: "transactionHash", 33 | }, 34 | txKeys: { 35 | to: "to", 36 | amount: "value", 37 | }, 38 | fixedEventData: { 39 | token: WETH, 40 | from: L1StandardBridge, 41 | }, 42 | isDeposit: false, 43 | }; 44 | 45 | const ercDepositParams: PartialContractEventParams = { 46 | target: L1StandardBridge, 47 | topic: "ERC20BridgeInitiated(address,address,address,address,uint256,bytes)", 48 | abi: ["event ERC20BridgeInitiated(address indexed localToken, address indexed remoteToken, address indexed from, address to, uint256 amount, bytes extraData)"], 49 | argKeys: { 50 | token: "localToken", 51 | amount: "amount", 52 | from: "from", 53 | }, 54 | logKeys: { 55 | blockNumber: "blockNumber", 56 | txHash: "transactionHash", 57 | }, 58 | fixedEventData: { 59 | to: L1StandardBridge, 60 | }, 61 | isDeposit: true, 62 | }; 63 | 64 | const ercWithdrawParams: PartialContractEventParams = { 65 | target: L1StandardBridge, 66 | topic: "ERC20BridgeFinalized(address,address,address,address,uint256,bytes)", 67 | abi: ["event ERC20BridgeFinalized(address indexed localToken, address indexed remoteToken, address indexed from, address to, uint256 amount, bytes extraData)"], 68 | argKeys: { 69 | token: "localToken", 70 | amount: "amount", 71 | to: "to", 72 | }, 73 | logKeys: { 74 | blockNumber: "blockNumber", 75 | txHash: "transactionHash", 76 | }, 77 | 78 | fixedEventData: { 79 | from: L1StandardBridge, 80 | }, 81 | isDeposit: false, 82 | }; 83 | 84 | const constructParams = () => { 85 | const eventParams = [ 86 | ethDepositParams, 87 | ethWithdrawParams, 88 | ercDepositParams, 89 | ercWithdrawParams, 90 | ]; 91 | return async (fromBlock: number, toBlock: number) => 92 | getTxDataFromEVMEventLogs("zircuit", "ethereum", fromBlock, toBlock, eventParams); 93 | }; 94 | 95 | const adapter: BridgeAdapter = { 96 | ethereum: constructParams(), 97 | }; 98 | 99 | export default adapter; 100 | -------------------------------------------------------------------------------- /src/adapters/zksync/index.ts: -------------------------------------------------------------------------------- 1 | import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type"; 2 | import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions"; 3 | import { constructTransferParams } from "../../helpers/eventParams"; 4 | 5 | /* 6 | 7 | 0x32400084C286CF3E17e7B677ea9583e60a000324 is zkSync Era: Diamond Proxy 8 | - deposits of 9 | - ETH 10 | 0xf8A16864D8De145A266a534174305f881ee2315e is zkSync Era: Withdrawal Finalizer 11 | 0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063 is zkSync Era: Bridge 12 | - deposits of 13 | - ERC20 14 | - withdrawals of 15 | - ETH 16 | - ERC20 17 | */ 18 | 19 | const WETH = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"; 20 | 21 | const ethDepositEventParams: PartialContractEventParams = { 22 | target: "0x32400084C286CF3E17e7B677ea9583e60a000324", 23 | // topic: "NewPriorityRequest(uint256,bytes32,uint64,tuple,bytes[])", // as shown on Etherscan 24 | topic: 25 | "NewPriorityRequest(uint256,bytes32,uint64,(uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes),bytes[])", // expand tuple data types 26 | // topic: "NewPriorityRequest(uint256,bytes32,uint64,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256,uint256[4],bytes,bytes,uint256[],bytes,bytes,bytes[])", // break out data types from tuple 27 | abi: [ 28 | // "event NewPriorityRequest(uint256 txId, bytes32 txHash, uint64 expirationTimestamp, tuple transaction, bytes[] factoryDeps)", // as shown on Etherscan 29 | "event NewPriorityRequest(uint256 txId, bytes32 txHash, uint64 expirationTimestamp, tuple(uint256 txType, uint256 from, uint256 to, uint256 gasLimit, uint256 gasPerPubdataByteLimit, uint256 maxFeePerGas, uint256 maxPriorityFeePerGas, uint256 paymaster, uint256 nonce, uint256 value, uint256[4] reserved, bytes data, bytes signature, uint256[] factoryDeps, bytes paymasterInput, bytes reservedDynamic) transaction, bytes[] factoryDeps)", 30 | ], 31 | argKeys: { 32 | to: "transaction[2]", 33 | amount: "transaction[9]", 34 | }, 35 | argGetters: { 36 | to: (log: any) => log?.transaction?.[2]?.toHexString(), 37 | amount: (log: any) => log?.transaction?.[9], 38 | }, 39 | logKeys: { 40 | blockNumber: "blockNumber", 41 | txHash: "transactionHash", 42 | }, 43 | fixedEventData: { 44 | from: "0x32400084C286CF3E17e7B677ea9583e60a000324", 45 | token: WETH, 46 | }, 47 | isDeposit: true, 48 | }; 49 | 50 | const erc20DepositEventParams: PartialContractEventParams = constructTransferParams( 51 | "0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063", 52 | true 53 | ); 54 | 55 | const ethWithdrawalEventParams: PartialContractEventParams = { 56 | target: "0x32400084C286CF3E17e7B677ea9583e60a000324", 57 | topic: "EthWithdrawalFinalized(address,uint256)", 58 | abi: ["event EthWithdrawalFinalized(address indexed to, uint256 amount)"], 59 | argKeys: { 60 | to: "to", 61 | amount: "amount", 62 | }, 63 | fixedEventData: { 64 | from: "0x32400084C286CF3E17e7B677ea9583e60a000324", 65 | token: WETH, 66 | }, 67 | isDeposit: false, 68 | }; 69 | 70 | const erc20WithdrawalEventParams: PartialContractEventParams = constructTransferParams( 71 | "0x57891966931Eb4Bb6FB81430E6cE0A03AAbDe063", 72 | false 73 | ); 74 | 75 | const constructParams = () => { 76 | const eventParams = [ 77 | ethDepositEventParams, 78 | erc20DepositEventParams, 79 | ethWithdrawalEventParams, 80 | erc20WithdrawalEventParams, 81 | ]; 82 | return async (fromBlock: number, toBlock: number) => 83 | getTxDataFromEVMEventLogs("zksync", "ethereum", fromBlock, toBlock, eventParams); 84 | }; 85 | 86 | const adapter: BridgeAdapter = { 87 | ethereum: constructParams(), 88 | }; 89 | 90 | export default adapter; 91 | -------------------------------------------------------------------------------- /src/cli/separateFiles.js: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | const retry = require("async-retry"); 3 | const { writeFileSync } = require("fs"); 4 | 5 | async function main(){ 6 | const recordedBlocks = ( 7 | await retry( 8 | async (_bail) => 9 | await axios.get("https://llama-bridges-data.s3.eu-central-1.amazonaws.com/recordedBlocks.json") 10 | ) 11 | ).data; 12 | const names = new Set(Object.keys(recordedBlocks).map(k=>k.split(":")[0])) 13 | for(const name of names){ 14 | const data = Object.fromEntries(Object.entries(recordedBlocks).filter(([k])=>k.startsWith(name))) 15 | writeFileSync(`blocks-${name}.json`, JSON.stringify(data)) 16 | } 17 | } 18 | main() -------------------------------------------------------------------------------- /src/data/blacklist.ts: -------------------------------------------------------------------------------- 1 | export const blacklist = [ 2 | "ethereum:0x6adb2e268de2aa1abf6578e4a8119b960e02928f", // SHIBDOGE 3 | ]; 4 | -------------------------------------------------------------------------------- /src/data/importBridgeNetwork.ts: -------------------------------------------------------------------------------- 1 | import bridgeNetworks from "./bridgeNetworkData" 2 | 3 | 4 | export const importBridgeNetwork = (bridgeDbName?: string, bridgeNetworkId?: number) => { 5 | if (bridgeDbName) { 6 | return bridgeNetworks.filter((bridgeNetwork) => bridgeNetwork.bridgeDbName === bridgeDbName)[0]; 7 | } 8 | if (bridgeNetworkId) { 9 | return bridgeNetworks.filter((bridgeNetwork) => bridgeNetwork.id === bridgeNetworkId)[0]; 10 | } 11 | return null 12 | } 13 | -------------------------------------------------------------------------------- /src/data/types.ts: -------------------------------------------------------------------------------- 1 | import { Chain } from "@defillama/sdk/build/general"; 2 | 3 | export type BridgeNetwork = { 4 | id: number; 5 | displayName: string; 6 | bridgeDbName: string; 7 | iconLink: string; 8 | largeTxThreshold: number; 9 | url: string; 10 | token?: string; 11 | symbol?: string; 12 | chains: string[]; 13 | chainMapping?: { 14 | [chain: string]: string; 15 | }; // used when overwriting adapter key (adapter key is always the chain volume counts for, can be overwritten to query blocks/contracts on a different chain) 16 | destinationChain?: string; // used to specify the destination chain when contracts on only 1 chain are tracked 17 | }; 18 | -------------------------------------------------------------------------------- /src/handlers/checkStaleBridges.ts: -------------------------------------------------------------------------------- 1 | import bridgeNetworks from "../data/bridgeNetworkData"; 2 | import { sendDiscordText } from "../utils/discord"; 3 | import { wrapScheduledLambda } from "../utils/wrap"; 4 | import { queryAggregatedDailyTimestampRange } from "../utils/wrappa/postgres/query"; 5 | 6 | const checkStaleBridges = async () => { 7 | const result: Record> = {}; 8 | const startTs = Math.floor(Date.now() / 1000 - 60 * 60 * 24 * 7); 9 | const endTs = Math.floor(Date.now() / 1000); 10 | const currentDate = new Date(); 11 | 12 | const queryPromises: Promise[] = []; 13 | 14 | for (const bridge of bridgeNetworks) { 15 | result[bridge.bridgeDbName] = {}; 16 | 17 | for (const chain of bridge.chains) { 18 | const queryPromise = (async () => { 19 | if (bridge.destinationChain === chain) return; 20 | try { 21 | const volume = await queryAggregatedDailyTimestampRange( 22 | startTs, 23 | endTs, 24 | chain?.toLowerCase(), 25 | bridge.bridgeDbName 26 | ); 27 | if (volume.length === 0) { 28 | result[bridge.bridgeDbName][chain] = 7; 29 | } else { 30 | let lastActiveDay = new Date(0); 31 | 32 | for (const day of volume) { 33 | if (day.total_deposit_txs > 0 || day.total_withdrawal_txs > 0) { 34 | lastActiveDay = new Date(day.ts); 35 | } 36 | } 37 | 38 | if (lastActiveDay.getTime() === 0) { 39 | result[bridge.bridgeDbName][chain] = 7; 40 | } else { 41 | const staleDays = Math.floor((currentDate.getTime() - lastActiveDay.getTime()) / (1000 * 3600 * 24)); 42 | result[bridge.bridgeDbName][chain] = staleDays; 43 | } 44 | } 45 | } catch (error) { 46 | console.error(`Error querying ${bridge.bridgeDbName} on ${chain}:`, error); 47 | result[bridge.bridgeDbName][chain] = -1; 48 | } 49 | })(); 50 | 51 | queryPromises.push(queryPromise); 52 | } 53 | } 54 | 55 | await Promise.all(queryPromises); 56 | 57 | console.log("Stale bridges report (3+ days stale):"); 58 | let hasStaleEntries = false; 59 | let discordMessage = "Bridges stale for 3+ days:\n"; 60 | 61 | for (const [bridgeName, chains] of Object.entries(result)) { 62 | let staleChains = []; 63 | 64 | for (const [chain, staleDays] of Object.entries(chains)) { 65 | if (staleDays >= 3) { 66 | hasStaleEntries = true; 67 | staleChains.push(chain); 68 | } 69 | } 70 | 71 | if (staleChains.length > 0) { 72 | discordMessage += `${bridgeName}: [${staleChains.join(", ")}]\n`; 73 | } 74 | } 75 | 76 | if (!hasStaleEntries) { 77 | console.log("No bridges are 3 or more days stale."); 78 | discordMessage = "No bridges are stale for 3+ days."; 79 | } 80 | 81 | if (discordMessage !== "Bridges stale for 3+ days:\n") { 82 | try { 83 | await sendDiscordText(discordMessage); 84 | console.log("Discord message prepared (not sent):", discordMessage); 85 | } catch (error) { 86 | console.error("Error sending Discord message:", error); 87 | } 88 | } 89 | 90 | return result; 91 | }; 92 | 93 | const handler = async () => { 94 | await checkStaleBridges(); 95 | }; 96 | 97 | export default wrapScheduledLambda(handler); 98 | -------------------------------------------------------------------------------- /src/handlers/dailyAggregateAllAdapters.ts: -------------------------------------------------------------------------------- 1 | import { wrapScheduledLambda } from "../utils/wrap"; 2 | import { convertToUnixTimestamp } from "../utils/date"; 3 | import { runAggregateDataHistorical } from "../utils/aggregate"; 4 | import bridgeNetworkData from "../data/bridgeNetworkData"; 5 | 6 | export default wrapScheduledLambda(async (_event) => { 7 | const fourHoursAgo = convertToUnixTimestamp(new Date()) - 60 * 60 * 4; 8 | const now = convertToUnixTimestamp(new Date()); 9 | 10 | const startTimestamp = fourHoursAgo; 11 | const endTimestamp = now; 12 | 13 | for (const adapter of bridgeNetworkData) { 14 | await runAggregateDataHistorical(startTimestamp, endTimestamp, adapter.id, false); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /src/handlers/getBridgeChains.ts: -------------------------------------------------------------------------------- 1 | import { IResponse, successResponse } from "../utils/lambda-response"; 2 | import wrap from "../utils/wrap"; 3 | import { getDailyBridgeVolume } from "../utils/bridgeVolume"; 4 | import { getChainDisplayName, chainCoingeckoIds } from "../utils/normalizeChain"; 5 | import { getCurrentUnixTimestamp, secondsInDay } from "../utils/date"; 6 | import bridgeNetworks from "../data/bridgeNetworkData"; 7 | import { normalizeChain } from "../utils/normalizeChain"; 8 | 9 | export async function craftBridgeChainsResponse() { 10 | const chainsMap = new Map(); 11 | const currentTimestamp = getCurrentUnixTimestamp(); 12 | 13 | await Promise.all( 14 | bridgeNetworks.map(async (bridgeNetwork) => { 15 | const { chains, destinationChain } = bridgeNetwork; 16 | 17 | if (destinationChain) { 18 | const normalizedName = normalizeChain(destinationChain); 19 | chainsMap.set(normalizedName, destinationChain); 20 | } 21 | 22 | chains.forEach((chain) => { 23 | const normalizedName = normalizeChain(chain); 24 | chainsMap.set(normalizedName, chain); 25 | }); 26 | }) 27 | ); 28 | 29 | const chainPromises = Promise.all( 30 | Array.from(chainsMap.keys()).map(async (normalizedChain) => { 31 | const chainName = getChainDisplayName(normalizedChain, true); 32 | if (chainCoingeckoIds[chainName] === undefined) { 33 | return; 34 | } 35 | 36 | const lastWeekDailyBridgeVolume = await getDailyBridgeVolume( 37 | currentTimestamp - 7 * secondsInDay, 38 | currentTimestamp, 39 | normalizedChain 40 | ); 41 | 42 | let volumePrevDay = 0; 43 | if (lastWeekDailyBridgeVolume.length > 1) { 44 | const lastDailyBridgeVolume = lastWeekDailyBridgeVolume[lastWeekDailyBridgeVolume.length - 1]; 45 | volumePrevDay = lastDailyBridgeVolume?.depositUSD + lastDailyBridgeVolume?.withdrawUSD; 46 | } 47 | 48 | return { 49 | gecko_id: chainCoingeckoIds[chainName]?.geckoId ?? null, 50 | volumePrevDay: volumePrevDay, 51 | tokenSymbol: chainCoingeckoIds[chainName]?.symbol ?? null, 52 | name: chainName, 53 | }; 54 | }) 55 | ); 56 | 57 | const response = (await chainPromises).filter((chain) => chain); 58 | 59 | return response; 60 | } 61 | 62 | const handler = async (_event: AWSLambda.APIGatewayEvent): Promise => { 63 | const chainData = await craftBridgeChainsResponse(); 64 | return successResponse(chainData, 10 * 60); // 10 mins cache 65 | }; 66 | 67 | export default wrap(handler); 68 | -------------------------------------------------------------------------------- /src/handlers/getBridgeVolume.ts: -------------------------------------------------------------------------------- 1 | import { IResponse, successResponse, errorResponse } from "../utils/lambda-response"; 2 | import wrap from "../utils/wrap"; 3 | import { getDailyBridgeVolume } from "../utils/bridgeVolume"; 4 | import { importBridgeNetwork } from "../data/importBridgeNetwork"; 5 | import { normalizeChain } from "../utils/normalizeChain"; 6 | import { DEFAULT_TTL } from "../utils/cache"; 7 | 8 | const getBridgeVolume = async (chain?: string, bridgeNetworkId?: string) => { 9 | if (!chain) { 10 | return errorResponse({ 11 | message: "Must include a chain or 'all' as path parameter.", 12 | }); 13 | } 14 | let bridgeNetwork; 15 | if (bridgeNetworkId) { 16 | bridgeNetwork = importBridgeNetwork(undefined, parseInt(bridgeNetworkId)); 17 | } 18 | const destinationChain = bridgeNetwork?.destinationChain; 19 | if (destinationChain && chain === destinationChain?.toLowerCase()) { 20 | chain = "all"; 21 | } 22 | const queryChain = chain === "all" ? undefined : normalizeChain(chain); 23 | 24 | const queryId = bridgeNetworkId ? parseInt(bridgeNetworkId) : undefined; 25 | if (bridgeNetworkId && queryId) { 26 | try { 27 | const bridgeNetwork = importBridgeNetwork(undefined, queryId); 28 | if (!bridgeNetwork) { 29 | if (!bridgeNetwork) { 30 | throw new Error("No bridge network found."); 31 | } 32 | } 33 | } catch (e) { 34 | return errorResponse({ 35 | message: "Invalid bridgeNetworkId entered.", 36 | }); 37 | } 38 | } 39 | const dailyVolumes = await getDailyBridgeVolume(undefined, undefined, queryChain, queryId); 40 | 41 | return dailyVolumes; 42 | }; 43 | 44 | const handler = async (event: AWSLambda.APIGatewayEvent): Promise => { 45 | const chain = event.pathParameters?.chain?.toLowerCase().replace(/%20/g, " "); 46 | const bridgeNetworkId = event.queryStringParameters?.id; 47 | 48 | const response = await getBridgeVolume(chain, bridgeNetworkId); 49 | return successResponse(response, DEFAULT_TTL); 50 | }; 51 | 52 | export default wrap(handler); 53 | -------------------------------------------------------------------------------- /src/handlers/getLargeTransactions.ts: -------------------------------------------------------------------------------- 1 | import { IResponse, successResponse } from "../utils/lambda-response"; 2 | import wrap from "../utils/wrap"; 3 | import { queryLargeTransactionsTimestampRange, queryConfig } from "../utils/wrappa/postgres/query"; 4 | import { getCurrentUnixTimestamp, convertToUnixTimestamp } from "../utils/date"; 5 | import { getLlamaPrices } from "../utils/prices"; 6 | import { transformTokens } from "../helpers/tokenMappings"; 7 | import { importBridgeNetwork } from "../data/importBridgeNetwork"; 8 | import { normalizeChain } from "../utils/normalizeChain"; 9 | 10 | const getLargeTransactions = async ( 11 | chain: string = "all", 12 | startTimestamp: string = "0", 13 | endTimestamp: string = "0" 14 | ) => { 15 | const queryStartTimestamp = parseInt(startTimestamp); 16 | const queryEndTimestamp = endTimestamp === "0" ? getCurrentUnixTimestamp() : parseInt(endTimestamp); 17 | const queryChain = chain === "all" ? null : normalizeChain(chain); 18 | 19 | const configs = await queryConfig(); 20 | let configMapping = {} as any; 21 | configs.map((config) => { 22 | const id = config.id; 23 | const name = config.bridge_name; 24 | const bridgeNetwork = importBridgeNetwork(name); 25 | if (bridgeNetwork) { 26 | const { displayName } = bridgeNetwork; 27 | configMapping[id] = displayName; 28 | } 29 | }); 30 | 31 | const largeTransactions = await queryLargeTransactionsTimestampRange( 32 | queryChain, 33 | queryStartTimestamp, 34 | queryEndTimestamp 35 | ); 36 | 37 | const tokenSet = new Set(); 38 | largeTransactions.map((tx) => { 39 | const symbol = transformTokens[tx.chain]?.[tx.token] 40 | ? transformTokens[tx.chain]?.[tx.token] 41 | : `${tx.chain}:${tx.token}`; 42 | tokenSet.add(symbol); 43 | }); 44 | const prices = await getLlamaPrices(Array.from(tokenSet)); 45 | const response = largeTransactions.map((tx) => { 46 | const bridgeID = tx.bridge_id; 47 | const bridgeName = configMapping[bridgeID] ?? "unknown"; 48 | const transformedToken = transformTokens[tx.chain]?.[tx.token] 49 | ? transformTokens[tx.chain]?.[tx.token] 50 | : `${tx.chain}:${tx.token}`; 51 | const symbol = prices?.[transformedToken]?.symbol ?? "unknown"; 52 | return { 53 | date: convertToUnixTimestamp(tx.ts), 54 | txHash: `${tx.chain}:${tx.tx_hash}`, 55 | from: tx.tx_from, 56 | to: tx.tx_to, 57 | token: `${tx.chain}:${tx.token}`, 58 | symbol: symbol, 59 | amount: tx.amount, 60 | isDeposit: tx.is_deposit, 61 | bridge: bridgeName, 62 | chain: tx.chain, 63 | usdValue: tx.usd_value, 64 | }; 65 | }); 66 | return response.slice(0, 2000); 67 | }; 68 | 69 | const handler = async (event: AWSLambda.APIGatewayEvent): Promise => { 70 | const chain = event.pathParameters?.chain?.toLowerCase(); 71 | const startTimestamp = event.queryStringParameters?.starttimestamp; 72 | const endTimestamp = event.queryStringParameters?.endtimestamp; 73 | const response = await getLargeTransactions(chain, startTimestamp, endTimestamp); 74 | return successResponse(response, 10 * 60); // 10 mins cache 75 | }; 76 | 77 | export default wrap(handler); 78 | -------------------------------------------------------------------------------- /src/handlers/getLastBlocks.ts: -------------------------------------------------------------------------------- 1 | import wrap, { wrapScheduledLambda } from "../utils/wrap"; 2 | import bridgeNetworks from "../data/bridgeNetworkData"; 3 | import { runAdapterToCurrentBlock } from "../utils/adapter"; 4 | import { sql } from "../utils/db"; 5 | import { successResponse } from "../utils/lambda-response"; 6 | 7 | const handler = async () => { 8 | const lastRecordedBlocks = ( 9 | await sql`SELECT jsonb_object_agg(bridge_id::text, subresult) as result 10 | FROM ( 11 | SELECT bridge_id, jsonb_build_object('startBlock', MIN(tx_block), 'endBlock', MAX(tx_block)) as subresult 12 | FROM bridges.transactions 13 | GROUP BY bridge_id 14 | ) subquery; 15 | ` 16 | )[0].result; 17 | 18 | const bridgeConfig = await sql`SELECT * FROM bridges.config`; 19 | 20 | const bridgeConfigById = bridgeConfig.reduce((acc: any, config: any) => { 21 | acc[config.id] = config; 22 | return acc; 23 | }, {}); 24 | 25 | const lastBlocksByName = Object.keys(lastRecordedBlocks).reduce((acc: any, bridgeId: any) => { 26 | acc[`${bridgeConfigById[bridgeId].bridge_name}-${bridgeConfigById[bridgeId].chain}`] = lastRecordedBlocks[bridgeId]; 27 | return acc; 28 | }, {}); 29 | return successResponse(lastBlocksByName); 30 | }; 31 | 32 | export default wrap(handler); 33 | -------------------------------------------------------------------------------- /src/handlers/getNetflows.ts: -------------------------------------------------------------------------------- 1 | import { IResponse, successResponse, errorResponse } from "../utils/lambda-response"; 2 | import wrap from "../utils/wrap"; 3 | import { getNetflows } from "../utils/wrappa/postgres/query"; 4 | 5 | const handler = async (event: AWSLambda.APIGatewayEvent): Promise => { 6 | const period = event.pathParameters?.period?.toLowerCase() as "day" | "week" | "month"; 7 | 8 | if (!period || !["day", "week", "month"].includes(period)) { 9 | return errorResponse({ 10 | message: "Period must be one of: day, week, month", 11 | }); 12 | } 13 | 14 | const response = await getNetflows(period); 15 | return successResponse(response, 10 * 60); // 10 mins cache 16 | }; 17 | 18 | export default wrap(handler); 19 | -------------------------------------------------------------------------------- /src/handlers/getTransactions.ts: -------------------------------------------------------------------------------- 1 | import { IResponse, successResponse, errorResponse } from "../utils/lambda-response"; 2 | import wrap from "../utils/wrap"; 3 | import { queryTransactionsTimestampRangeByBridgeNetwork } from "../utils/wrappa/postgres/query"; 4 | import { importBridgeNetwork } from "../data/importBridgeNetwork"; 5 | import { normalizeChain } from "../utils/normalizeChain"; 6 | 7 | const maxResponseTxs = 6000; // maximum number of transactions to return 8 | 9 | const getTransactions = async ( 10 | startTimestamp?: string, 11 | endTimestamp?: string, 12 | bridgeNetworkId?: string, 13 | chain?: string, 14 | sourceChain?: string, 15 | address?: string, 16 | limit?: string 17 | ) => { 18 | if (bridgeNetworkId && !(bridgeNetworkId === "all") && isNaN(parseInt(bridgeNetworkId))) { 19 | return errorResponse({ 20 | message: "Invalid Bridge ID entered. Use Bridge ID from 'bridges' endpoint as path param, or `all`.", 21 | }); 22 | } 23 | 24 | if (chain && sourceChain) { 25 | return errorResponse({ 26 | message: "Cannot include both 'chain' and 'sourceChain' as query params.", 27 | }); 28 | } 29 | const queryStartTimestamp = startTimestamp ? parseInt(startTimestamp) : 0; 30 | const queryEndTimestamp = endTimestamp ? parseInt(endTimestamp) : undefined; 31 | const queryChain = sourceChain ? normalizeChain(sourceChain) : chain ? normalizeChain(chain) : chain; 32 | let queryName = undefined; 33 | if (bridgeNetworkId && !isNaN(parseInt(bridgeNetworkId))) { 34 | const bridgeNetwork = importBridgeNetwork(undefined, parseInt(bridgeNetworkId)); 35 | const { bridgeDbName } = bridgeNetwork!; 36 | queryName = bridgeDbName; 37 | } 38 | let addressChain = undefined as unknown; 39 | let addressHash = undefined as unknown; 40 | if (typeof address === "string") { 41 | [addressChain, addressHash] = address?.split(":"); 42 | } 43 | const integerLimit = isNaN(parseInt(limit ?? "100000")) ? maxResponseTxs : parseInt(limit ?? "100000"); 44 | const responseLimit = Math.min(maxResponseTxs, integerLimit); 45 | 46 | const transactions = (await queryTransactionsTimestampRangeByBridgeNetwork( 47 | queryStartTimestamp, 48 | queryEndTimestamp, 49 | queryName, 50 | queryChain 51 | )) as any[]; 52 | 53 | const response = transactions 54 | .map((tx) => { 55 | delete tx.bridge_id; 56 | if (sourceChain) { 57 | tx.sourceChain = sourceChain; 58 | if ((tx.is_deposit && sourceChain === tx.chain) || (!tx.is_deposit && sourceChain === tx.destination_chain)) { 59 | delete tx.is_deposit; 60 | } else return null; 61 | } 62 | delete tx.destination_chain; 63 | if (addressHash) { 64 | if ( 65 | !( 66 | (addressHash === tx.tx_to.toLowerCase() || addressHash === tx.tx_from.toLowerCase()) && 67 | addressChain === tx.chain 68 | ) 69 | ) 70 | return null; 71 | } 72 | return tx; 73 | }) 74 | .filter((tx) => tx) 75 | .slice(0, responseLimit); 76 | 77 | return response; 78 | }; 79 | 80 | const handler = async (event: AWSLambda.APIGatewayEvent): Promise => { 81 | const id = event.pathParameters?.id?.toLowerCase(); 82 | const startTimestamp = event.queryStringParameters?.starttimestamp; 83 | const endTimestamp = event.queryStringParameters?.endtimestamp; 84 | const chain = event.queryStringParameters?.chain?.toLowerCase(); 85 | const sourceChain = event.queryStringParameters?.sourcechain?.toLowerCase(); 86 | const address = event.queryStringParameters?.address?.toLowerCase(); 87 | const limit = event.queryStringParameters?.limit; 88 | const response = await getTransactions(startTimestamp, endTimestamp, id, chain, sourceChain, address, limit); 89 | return successResponse(response, 10 * 60); // 10 mins cache 90 | }; 91 | 92 | export default wrap(handler); 93 | -------------------------------------------------------------------------------- /src/handlers/runAdapter.ts: -------------------------------------------------------------------------------- 1 | import { wrapScheduledLambda } from "../utils/wrap"; 2 | import bridgeNetworks from "../data/bridgeNetworkData"; 3 | import { runAdapterToCurrentBlock } from "../utils/adapter"; 4 | import { sql } from "../utils/db"; 5 | import { BridgeNetwork } from "../data/types"; 6 | 7 | const handler = async (event: any) => { 8 | try { 9 | const bridgesToRun = event.bridgeIndices.map((index: number) => bridgeNetworks[index]); 10 | const promises = Promise.all( 11 | bridgesToRun.map(async (bridge: BridgeNetwork) => { 12 | return runAdapterToCurrentBlock(bridge, true, "ignore", event.lastRecordedBlocks); 13 | }) 14 | ); 15 | await promises; 16 | console.log(`Adapters of ${bridgesToRun.map((bridge: any) => bridge.bridgeDbName).join(", ")} ran successfully`); 17 | } catch (e) { 18 | console.error(`Adapter ${bridgeNetworks[event.bridgeIndex].bridgeDbName} failed ${JSON.stringify(e)}`); 19 | } 20 | }; 21 | 22 | export default wrapScheduledLambda(handler); 23 | -------------------------------------------------------------------------------- /src/handlers/runAdapterByName.ts: -------------------------------------------------------------------------------- 1 | import { wrapScheduledLambda } from "../utils/wrap"; 2 | import bridgeNetworks from "../data/bridgeNetworkData"; 3 | import { runAdapterToCurrentBlock } from "../utils/adapter"; 4 | import { sql } from "../utils/db"; 5 | import { BridgeNetwork } from "../data/types"; 6 | 7 | const handler = async (event: { bridgeName: string }) => { 8 | try { 9 | const { bridgeName } = event; 10 | 11 | if (!bridgeName) { 12 | throw new Error("Bridge name is required"); 13 | } 14 | 15 | const bridge = bridgeNetworks.find((b: BridgeNetwork) => b.bridgeDbName === bridgeName); 16 | 17 | if (!bridge) { 18 | throw new Error(`Bridge ${bridgeName} not found`); 19 | } 20 | 21 | const lastRecordedBlocks = await sql`SELECT jsonb_object_agg(bridge_id::text, subresult) as result 22 | FROM ( 23 | SELECT bridge_id, jsonb_build_object('startBlock', MIN(tx_block), 'endBlock', MAX(tx_block)) as subresult 24 | FROM bridges.transactions 25 | GROUP BY bridge_id 26 | ) subquery; 27 | `; 28 | 29 | console.log(`Running adapter for ${bridgeName}`); 30 | await runAdapterToCurrentBlock(bridge, true, "ignore", lastRecordedBlocks[0].result); 31 | console.log(`Adapter for ${bridgeName} ran successfully`); 32 | } catch (e) { 33 | console.error(`Adapter ${event.bridgeName} failed: ${JSON.stringify(e)}`); 34 | throw e; 35 | } 36 | }; 37 | 38 | export default wrapScheduledLambda(handler); 39 | -------------------------------------------------------------------------------- /src/handlers/runAdapterFromTo.ts: -------------------------------------------------------------------------------- 1 | import { wrapScheduledLambda } from "../utils/wrap"; 2 | import bridgeNetworks from "../data/bridgeNetworkData"; 3 | import { runAdapterHistorical } from "../utils/adapter"; 4 | import { sql } from "../utils/db"; 5 | import { getBridgeID } from "../utils/wrappa/postgres/query"; 6 | import { getLatestBlock } from "../utils/blocks"; 7 | import { chainMappings } from "../helpers/tokenMappings"; 8 | 9 | const handler = async (event: any) => { 10 | try { 11 | const { bridgeName, fromTimestamp, toTimestamp } = event; 12 | 13 | const adapter = bridgeNetworks.find((x) => x.bridgeDbName === bridgeName); 14 | if (!adapter) throw new Error("Invalid adapter"); 15 | 16 | console.log(`Running adapter for ${bridgeName} from timestamp ${fromTimestamp} to ${toTimestamp}`); 17 | 18 | const promises = adapter.chains.map(async (chain) => { 19 | let nChain; 20 | if (chainMappings[chain.toLowerCase()]) { 21 | nChain = chainMappings[chain.toLowerCase()]; 22 | } else { 23 | nChain = chain.toLowerCase(); 24 | } 25 | if (nChain === adapter?.destinationChain?.toLowerCase()) return; 26 | 27 | console.log(`Processing chain ${nChain} for ${bridgeName}`); 28 | 29 | const bridgeConfig = await getBridgeID(bridgeName, nChain); 30 | if (!bridgeConfig) { 31 | console.error(`Could not find bridge config for ${nChain} on ${bridgeName}`); 32 | return; 33 | } 34 | let fromBlock, toBlock; 35 | if (fromTimestamp) { 36 | const fromTx = await sql<{ tx_block: number }[]>` 37 | SELECT tx_block FROM bridges.transactions 38 | WHERE bridge_id = ${bridgeConfig.id} 39 | AND chain = ${nChain} 40 | AND tx_block IS NOT NULL 41 | AND ts <= to_timestamp(${fromTimestamp}) 42 | ORDER BY ts DESC LIMIT 1 43 | `; 44 | fromBlock = fromTx[0].tx_block; 45 | } 46 | if (toTimestamp) { 47 | const toTx = await sql<{ tx_block: number }[]>` 48 | SELECT tx_block FROM bridges.transactions 49 | WHERE bridge_id = ${bridgeConfig.id} 50 | AND chain = ${nChain} 51 | AND tx_block IS NOT NULL 52 | AND ts >= to_timestamp(${toTimestamp}) 53 | ORDER BY ts ASC LIMIT 1 54 | `; 55 | toBlock = toTx[0].tx_block; 56 | } else { 57 | const latestBlock = await getLatestBlock(nChain); 58 | toBlock = latestBlock.number; 59 | } 60 | 61 | if (!fromBlock || !toBlock) { 62 | console.error(`Could not find transactions with blocks for ${nChain} on ${bridgeName}`); 63 | return; 64 | } 65 | 66 | await runAdapterHistorical(fromBlock, toBlock, adapter.id, nChain, true, false, "upsert"); 67 | 68 | console.log(`Adapter ${bridgeName} ran successfully for chain ${nChain} from block ${fromBlock} to ${toBlock}`); 69 | }); 70 | 71 | await Promise.all(promises); 72 | 73 | console.log(`Adapter ${bridgeName} completed for all chains`); 74 | } catch (e) { 75 | console.error(`Adapter failed: ${JSON.stringify(e)}`); 76 | } 77 | }; 78 | 79 | export default wrapScheduledLambda(handler); 80 | -------------------------------------------------------------------------------- /src/handlers/runAggregateAllAdapters.ts: -------------------------------------------------------------------------------- 1 | import { wrapScheduledLambda } from "../utils/wrap"; 2 | import { convertToUnixTimestamp } from "../utils/date"; 3 | import { runAggregateDataAllAdapters } from "../utils/aggregate"; 4 | 5 | export default wrapScheduledLambda(async (_event) => { 6 | const currentDate = new Date(); 7 | const currentTimestamp = convertToUnixTimestamp(currentDate) - 3600; 8 | await runAggregateDataAllAdapters(currentTimestamp, true); 9 | const currentHour = currentDate.getUTCHours(); 10 | if (currentHour === 0) { 11 | await runAggregateDataAllAdapters(currentTimestamp, false); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /src/handlers/runAllAdapters.ts: -------------------------------------------------------------------------------- 1 | import { wrapScheduledLambda } from "../utils/wrap"; 2 | import bridgeNetworks from "../data/bridgeNetworkData"; 3 | import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda"; 4 | import { sql } from "../utils/db"; 5 | import { store } from "../utils/s3"; 6 | 7 | const lambdaClient = new LambdaClient({}); 8 | 9 | async function invokeLambda(functionName: string, event: any) { 10 | const command = new InvokeCommand({ 11 | FunctionName: functionName, 12 | InvocationType: "Event", 13 | Payload: Buffer.from(JSON.stringify(event, null, 2)), 14 | }); 15 | 16 | try { 17 | const data = await lambdaClient.send(command); 18 | console.log(data); 19 | return data; 20 | } catch (error) { 21 | console.error(error); 22 | throw error; 23 | } 24 | } 25 | 26 | export default wrapScheduledLambda(async (event) => { 27 | const lastRecordedBlocks = await sql`SELECT jsonb_object_agg(bridge_id::text, subresult) as result 28 | FROM ( 29 | SELECT bridge_id, jsonb_build_object('startBlock', MIN(tx_block), 'endBlock', MAX(tx_block)) as subresult 30 | FROM bridges.transactions 31 | WHERE origin_chain IS NULL 32 | GROUP BY bridge_id 33 | ) subquery; 34 | `; 35 | try { 36 | const bridgeConfig = await sql`SELECT * FROM bridges.config`; 37 | 38 | const bridgeConfigById = bridgeConfig.reduce((acc: any, config: any) => { 39 | acc[config.id] = config; 40 | return acc; 41 | }, {}); 42 | 43 | const lastBlocksByName = Object.keys(lastRecordedBlocks[0].result).reduce((acc: any, bridgeId: any) => { 44 | acc[`${bridgeConfigById[bridgeId].bridge_name}-${bridgeConfigById[bridgeId].chain}`] = 45 | lastRecordedBlocks[0].result[bridgeId]; 46 | return acc; 47 | }, {}); 48 | await store("lastRecordedBlocks.json", JSON.stringify(lastBlocksByName)); 49 | console.log("Stored last recorded blocks"); 50 | } catch (e) { 51 | console.error("Failed to store last recorded blocks"); 52 | console.error(e); 53 | } 54 | 55 | if (event?.bridgeName) { 56 | const bridge = bridgeNetworks.findIndex((x) => x.bridgeDbName === event.bridgeName); 57 | if (!bridge) { 58 | throw new Error(`Bridge ${event.bridgeName} not found`); 59 | } 60 | await invokeLambda("llama-bridges-prod-runAdapter", { 61 | bridgeIndices: [bridge], 62 | lastRecordedBlocks: lastRecordedBlocks[0].result, 63 | }); 64 | return; 65 | } 66 | 67 | const bridgeIndices = bridgeNetworks.map((_, i) => i); 68 | const randomIndices = bridgeIndices.sort(() => Math.random() - 0.5); 69 | 70 | const groupedIndices = []; 71 | for (let i = 0; i < randomIndices.length; i += 3) { 72 | groupedIndices.push(randomIndices.slice(i, i + 3)); 73 | } 74 | 75 | for (const group of groupedIndices) { 76 | await invokeLambda("llama-bridges-prod-runAdapter", { 77 | bridgeIndices: group, 78 | lastRecordedBlocks: lastRecordedBlocks[0].result, 79 | }); 80 | } 81 | }); 82 | -------------------------------------------------------------------------------- /src/handlers/runAllAdaptersHistorical.ts: -------------------------------------------------------------------------------- 1 | import { wrapScheduledLambda } from "../utils/wrap"; 2 | import bridgeNetworks from "../data/bridgeNetworkData"; 3 | import { LambdaClient, InvokeCommand } from "@aws-sdk/client-lambda"; 4 | 5 | const lambdaClient = new LambdaClient({}); 6 | 7 | async function invokeLambda(functionName: string, event: any) { 8 | const command = new InvokeCommand({ 9 | FunctionName: functionName, 10 | InvocationType: "Event", 11 | Payload: Buffer.from(JSON.stringify(event, null, 2)), 12 | }); 13 | 14 | try { 15 | const data = await lambdaClient.send(command); 16 | console.log(data); 17 | return data; 18 | } catch (error) { 19 | console.error(error); 20 | throw error; 21 | } 22 | } 23 | 24 | const handler = async (_event: any) => { 25 | const now = Math.floor(Date.now() / 1000); 26 | const fourHoursAgo = now - 60 * 60; 27 | 28 | for (const bridge of bridgeNetworks) { 29 | await invokeLambda("llama-bridges-prod-runAdapterFromTo", { 30 | bridgeName: bridge.bridgeDbName, 31 | fromTimestamp: fourHoursAgo, 32 | }); 33 | } 34 | 35 | console.log("Initiated historical runs for all adapters"); 36 | }; 37 | 38 | export default wrapScheduledLambda(handler); 39 | -------------------------------------------------------------------------------- /src/handlers/runHyperlane.ts: -------------------------------------------------------------------------------- 1 | import { insertConfigEntriesForAdapter } from "../utils/adapter"; 2 | import { build, getEvents } from "../adapters/hyperlane"; 3 | import dayjs from "dayjs"; 4 | import _ from "lodash"; 5 | import { insertTransactionRows } from "../utils/wrappa/postgres/write"; 6 | import { getBridgeID } from "../utils/wrappa/postgres/query"; 7 | import { sql } from "../utils/db"; 8 | 9 | export const handler = async () => { 10 | try { 11 | const adapter = await build(); 12 | await insertConfigEntriesForAdapter(adapter, "hyperlane"); 13 | const bridgeIds = Object.fromEntries( 14 | await Promise.all( 15 | Object.keys(adapter).map(async (chain) => { 16 | return [chain, (await getBridgeID("hyperlane", chain)).id]; 17 | }) 18 | ) 19 | ); 20 | console.log(bridgeIds); 21 | let startTs = dayjs().subtract(48, "hours").unix(); 22 | const endTs = dayjs().unix(); 23 | 24 | while (startTs < endTs) { 25 | const toTs = startTs + 60 * 60; 26 | const events = await getEvents(startTs, toTs); 27 | const transactions = events.map((event) => ({ 28 | bridge_id: bridgeIds[event.chain!], 29 | chain: event.chain!, 30 | tx_hash: event.txHash, 31 | ts: event.timestamp! * 1000, 32 | tx_block: event.blockNumber, 33 | tx_from: event.from, 34 | tx_to: event.to, 35 | token: event.token, 36 | amount: event.amount.toString(), 37 | is_deposit: event.isDeposit, 38 | is_usd_volume: true, 39 | txs_counted_as: 1, 40 | origin_chain: null, 41 | })); 42 | startTs = toTs; 43 | await sql.begin(async (sql) => { 44 | const batchSize = 200; 45 | const transactionChunks = _.chunk(transactions, batchSize); 46 | for (const batch of transactionChunks) { 47 | await insertTransactionRows(sql, true, batch, "upsert"); 48 | } 49 | }); 50 | console.log(`Inserted ${transactions.length} transactions for ${startTs} to ${endTs}`); 51 | } 52 | } catch (error) { 53 | console.error(error); 54 | throw error; 55 | } 56 | }; 57 | 58 | export default handler; 59 | -------------------------------------------------------------------------------- /src/helpers/allium.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const token = {} as Record; 4 | 5 | const HEADERS = { 6 | "Content-Type": "application/json", 7 | "X-API-KEY": process.env.ALLIUM_API_KEY, 8 | } as Record; 9 | 10 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 11 | 12 | async function startAlliumQuery(sqlQuery: string) { 13 | const query = await axios.post( 14 | `https://api.allium.so/api/v1/explorer/queries/phBjLzIZ8uUIDlp0dD3N/run-async`, 15 | { 16 | parameters: { 17 | fullQuery: sqlQuery, 18 | }, 19 | }, 20 | { 21 | headers: HEADERS, 22 | } 23 | ); 24 | 25 | return query.data["run_id"]; 26 | } 27 | 28 | async function retrieveAlliumResults(queryId: string) { 29 | const results = await axios.get(`https://api.allium.so/api/v1/explorer/query-runs/${queryId}/results?f=json`, { 30 | headers: HEADERS, 31 | }); 32 | return results.data.data; 33 | } 34 | 35 | async function cancelAlliumQuery(queryId: string) { 36 | const response = await axios.post(`https://api.allium.so/api/v1/explorer/query-runs/${queryId}/cancel`, { 37 | headers: HEADERS, 38 | }); 39 | return response.data; 40 | } 41 | 42 | async function queryAllium(sqlQuery: string) { 43 | const startTime = Date.now(); 44 | for (let i = 0; i < 10; i++) { 45 | console.log(`Querying Allium. Attempt ${i}`); 46 | if (!token[sqlQuery]) { 47 | token[sqlQuery] = await startAlliumQuery(sqlQuery); 48 | } 49 | 50 | if (!token[sqlQuery]) { 51 | throw new Error("Couldn't get a token from allium"); 52 | } 53 | 54 | const statusReq = await axios.get(`https://api.allium.so/api/v1/explorer/query-runs/${token[sqlQuery]}/status`, { 55 | headers: HEADERS, 56 | }); 57 | const status = statusReq.data; 58 | if (status === "success") { 59 | try { 60 | const results = await retrieveAlliumResults(token[sqlQuery]); 61 | delete token[sqlQuery]; 62 | return results; 63 | } catch (e) { 64 | console.log("query result", e); 65 | throw e; 66 | } 67 | } else if (status === "failed") { 68 | delete token[sqlQuery]; 69 | continue; 70 | } 71 | await sleep(20e3); 72 | } 73 | await cancelAlliumQuery(token[sqlQuery]); 74 | console.log(`Query ${sqlQuery} took ${(Date.now() - startTime) / 1000}s`); 75 | throw new Error("Not processed in time"); 76 | } 77 | 78 | export { queryAllium, startAlliumQuery, retrieveAlliumResults }; 79 | -------------------------------------------------------------------------------- /src/helpers/blockscout.ts: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | const endpoints = { 4 | manta: "https://pacific-explorer.manta.network/api/v2", 5 | // mantle: "https://pacific-explorer.manta.network/api/v2", 6 | rootstock: "", 7 | } as { [chain: string]: string }; -------------------------------------------------------------------------------- /src/helpers/btr.ts: -------------------------------------------------------------------------------- 1 | import { FunctionSignatureFilter } from "./bridgeAdapter.type"; 2 | const axios = require("axios"); 3 | const retry = require('retry') 4 | 5 | export const getTxsBlockRangeBtrScan = async ( 6 | address: string, 7 | startBlock: number, 8 | endBlock: number, 9 | functionSignatureFilter?: FunctionSignatureFilter 10 | ) => { 11 | let txList: any[] = await getBlockTXbyAddress(address, startBlock, endBlock); 12 | if (txList.length > 0) { 13 | const filteredResults = txList.filter((tx: any) => { 14 | if (functionSignatureFilter) { 15 | const signature = tx.input.slice(0, 10); 16 | if ( 17 | functionSignatureFilter.includeSignatures && 18 | !functionSignatureFilter.includeSignatures.includes(signature) 19 | ) { 20 | return false; 21 | } 22 | if ( 23 | functionSignatureFilter.excludeSignatures && 24 | functionSignatureFilter.excludeSignatures.includes(signature) 25 | ) { 26 | return false; 27 | } 28 | } 29 | return true; 30 | }); 31 | return filteredResults; 32 | } else { 33 | console.info(`No txs found for address ${address}.`); 34 | return []; 35 | } 36 | }; 37 | 38 | const getBlockTXbyAddress = async ( 39 | address: string, 40 | startBlock: number, 41 | endBlock: number, 42 | ) => { 43 | let res = await axios.get( 44 | `https://api.btrscan.com/scan/api?module=account&action=txlist&address=${address}&startblock=${startBlock}&endblock=${endBlock}&sort=asc` 45 | ) 46 | const data = res.data 47 | if(data.message == 'OK' && data.status == 1 && data.result.length != 0 ) { 48 | //filter by address 49 | const txList: any[] = data.result; 50 | return txList; 51 | } 52 | return [] 53 | } -------------------------------------------------------------------------------- /src/helpers/eventParams.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PartialContractEventParams, 3 | EventLogFilter, 4 | FunctionSignatureFilter 5 | } from "./bridgeAdapter.type"; 6 | import { Chain } from "@defillama/sdk/build/general"; 7 | 8 | export const constructTransferParams = ( 9 | target: string, 10 | isDeposit: boolean, 11 | filter?: EventLogFilter, 12 | functionSignatureFilter?: FunctionSignatureFilter, 13 | chain?: Chain, 14 | ) => { 15 | return Object.fromEntries( 16 | Object.entries({ 17 | target: target, 18 | contractChain: chain, 19 | isDeposit: isDeposit, 20 | isTransfer: true, 21 | filter: filter, 22 | functionSignatureFilter: functionSignatureFilter, 23 | }).filter(([_k, v]) => v != null) 24 | ) as PartialContractEventParams; 25 | }; 26 | -------------------------------------------------------------------------------- /src/helpers/l2scan.ts: -------------------------------------------------------------------------------- 1 | import { FunctionSignatureFilter } from "./bridgeAdapter.type"; 2 | const axios = require("axios"); 3 | const retry = require("async-retry"); 4 | 5 | const endpoints = { 6 | merlin: "https://scan.merlinchain.io", 7 | "b2-mainnet": "https://explorer.bsquared.network", 8 | "rsk": "https://rootstock.blockscout.com", 9 | } as { [chain: string]: string }; 10 | 11 | export const getTxsBlockRangeL2Scan = async ( 12 | chain: string, 13 | address: string, 14 | startBlock: number, 15 | endBlock: number, 16 | functionSignatureFilter?: FunctionSignatureFilter 17 | ) => { 18 | let txList: any[] = await getBlockTXbyAddress(chain, address, startBlock, endBlock); 19 | // console.log(JSON.stringify(txList)); 20 | if (txList.length > 0) { 21 | const filteredResults = txList.filter((tx: any) => { 22 | if (functionSignatureFilter) { 23 | const signature = tx.input.slice(0, 10); 24 | if ( 25 | functionSignatureFilter.includeSignatures && 26 | !functionSignatureFilter.includeSignatures.includes(signature) 27 | ) { 28 | return false; 29 | } 30 | if ( 31 | functionSignatureFilter.excludeSignatures && 32 | functionSignatureFilter.excludeSignatures.includes(signature) 33 | ) { 34 | return false; 35 | } 36 | } 37 | return true; 38 | }); 39 | return filteredResults; 40 | } else { 41 | console.info(`No txs found for address ${address}.`); 42 | return []; 43 | } 44 | }; 45 | 46 | const getBlockTXbyAddress = async ( 47 | chain: string, 48 | address: string, 49 | startBlock: number, 50 | endBlock: number, 51 | ) => { 52 | const endpoint = endpoints[chain]; 53 | let txList: any[] = [] 54 | let page = 1 55 | while(true) { 56 | let res = await retry( 57 | () => 58 | axios.get( 59 | `${endpoint}/api?module=account&action=txlist&address=${address}&endblock=${endBlock}&sort=asc&startblock=${startBlock}&offset=1000&page=${page}` 60 | ), 61 | { factor: 1, retries: 3 } 62 | ) 63 | if (res.data.message == 'OK' && res.data.result.length != 0) { 64 | txList = txList.concat(res.data.result) 65 | } else { 66 | break; 67 | } 68 | page++ 69 | } 70 | return txList; 71 | } -------------------------------------------------------------------------------- /src/helpers/merlin.ts: -------------------------------------------------------------------------------- 1 | import { FunctionSignatureFilter } from "./bridgeAdapter.type"; 2 | const axios = require("axios"); 3 | const retry = require("async-retry"); 4 | 5 | export const getTxsBlockRangeMerlinScan = async ( 6 | address: string, 7 | startBlock: number, 8 | endBlock: number, 9 | functionSignatureFilter?: FunctionSignatureFilter 10 | ) => { 11 | let txList: any[] = await getBlockTXbyAddress(address, startBlock, endBlock); 12 | // console.log(JSON.stringify(txList)); 13 | if (txList.length > 0) { 14 | const filteredResults = txList.filter((tx: any) => { 15 | if (functionSignatureFilter) { 16 | const signature = tx.input.slice(0, 10); 17 | if ( 18 | functionSignatureFilter.includeSignatures && 19 | !functionSignatureFilter.includeSignatures.includes(signature) 20 | ) { 21 | return false; 22 | } 23 | if ( 24 | functionSignatureFilter.excludeSignatures && 25 | functionSignatureFilter.excludeSignatures.includes(signature) 26 | ) { 27 | return false; 28 | } 29 | } 30 | return true; 31 | }); 32 | return filteredResults; 33 | } else { 34 | console.info(`No txs found for address ${address}.`); 35 | return []; 36 | } 37 | }; 38 | 39 | const getBlockTXbyAddress = async ( 40 | address: string, 41 | startBlock: number, 42 | endBlock: number, 43 | ) => { 44 | let txList: any[] = [] 45 | let page = 1 46 | while(true) { 47 | let res = await retry( 48 | () => 49 | axios.get( 50 | `https://scan.merlinchain.io/api?module=account&action=txlist&address=${address}&endblock=${endBlock}&sort=asc&startblock=${startBlock}&offset=1000&page=${page}` 51 | ), 52 | { factor: 1, retries: 3 } 53 | ) 54 | if (res.data.message == 'OK' && res.data.result.length != 0) { 55 | txList = txList.concat(res.data.result) 56 | } else { 57 | break; 58 | } 59 | page++ 60 | } 61 | return txList; 62 | } -------------------------------------------------------------------------------- /src/helpers/solana.ts: -------------------------------------------------------------------------------- 1 | import { Connection } from "@solana/web3.js"; 2 | 3 | export const getConnection = (): Connection => { 4 | const rpc = "https://api.mainnet-beta.solana.com"; 5 | const connection = new Connection(rpc); 6 | const getBlock = async (block: number) => { 7 | return new Connection(rpc).getBlock(block, { 8 | maxSupportedTransactionVersion: 0, 9 | }) as any; 10 | }; 11 | 12 | connection.getBlock = getBlock; 13 | return connection; 14 | }; 15 | -------------------------------------------------------------------------------- /src/helpers/sui.ts: -------------------------------------------------------------------------------- 1 | // import { 2 | // SuiClient, 3 | // SuiTransactionBlockResponse, 4 | // getFullnodeUrl, 5 | // PaginatedTransactionResponse, 6 | // } from "@mysten/sui.js/client"; 7 | 8 | // export const getClient = () => { 9 | // const url = process.env.SUI_RPC ?? getFullnodeUrl("mainnet"); 10 | // return new SuiClient({ url }); 11 | // }; 12 | 13 | // export const getTransactionBlocks = async ( 14 | // fromCheckpoint: number, 15 | // toCheckpoint: number, 16 | // changedObject: string 17 | // ): Promise => { 18 | // const client = getClient(); 19 | // const results: SuiTransactionBlockResponse[] = []; 20 | // let hasNextPage = false; 21 | // let cursor: string | null | undefined = undefined; 22 | // let oldestCheckpoint: string | null = null; 23 | // do { 24 | // // TODO: The public RPC doesn't support fetching events by chaining filters with a `TimeRange` filter, 25 | // // so we have to search backwards for our checkpoint range 26 | // const response: PaginatedTransactionResponse = await client.queryTransactionBlocks({ 27 | // filter: { ChangedObject: changedObject }, 28 | // cursor, 29 | // options: { 30 | // showEffects: true, 31 | // showEvents: true, 32 | // showInput: true, 33 | // showObjectChanges: true, 34 | // }, 35 | // }); 36 | // for (const txBlock of response.data) { 37 | // const checkpoint = txBlock.checkpoint; 38 | // if (!checkpoint) { 39 | // continue; 40 | // } 41 | // if (checkpoint >= fromCheckpoint.toString() && checkpoint <= toCheckpoint.toString()) { 42 | // results.push(txBlock); 43 | // } 44 | // if (oldestCheckpoint === null || checkpoint < oldestCheckpoint) { 45 | // oldestCheckpoint = checkpoint; 46 | // } 47 | // } 48 | // hasNextPage = response.hasNextPage; 49 | // cursor = response.nextCursor; 50 | // } while (hasNextPage && cursor && oldestCheckpoint && oldestCheckpoint >= fromCheckpoint.toString()); 51 | // return results; 52 | // }; 53 | -------------------------------------------------------------------------------- /src/helpers/tron.ts: -------------------------------------------------------------------------------- 1 | const axios = require("axios"); 2 | 3 | const apiKeys = [ 4 | "a4e25c66-b143-4d0b-91d2-0d9b2371d397", 5 | "1b2f1df4-c5bd-40b4-b416-118d7d6d3b51", 6 | "394bfb17-915c-4e38-82b0-61be1f8213c0", 7 | ]; 8 | 9 | export const getAccountTrcTransactions = async ( 10 | address: string, 11 | contractAddress: string, 12 | minTimestamp: number, 13 | maxTimestamp?: number 14 | ) => { 15 | let transactions = [] as any; 16 | for (let i = 0; i < 4; i++) { 17 | let accumulatedTransactions = [] as any[]; 18 | let fingerprint = null; 19 | try { 20 | do { 21 | const options = { 22 | method: "GET", 23 | headers: { "TRON-PRO-API-KEY": apiKeys[Math.floor(Math.random() * 3)], accept: "application/json" }, 24 | }; 25 | const response = await axios.get( 26 | `https://api.trongrid.io/v1/accounts/${address}/transactions/trc20?limit=50${ 27 | fingerprint ? `&fingerprint=${fingerprint}` : "" 28 | }&min_timestamp=${minTimestamp}${ 29 | maxTimestamp ? `&max_timestamp=${maxTimestamp}` : "" 30 | }&contract_address=${contractAddress}`, 31 | options 32 | ); 33 | if (response?.status === 200) { 34 | accumulatedTransactions = [...accumulatedTransactions, ...response.data.data]; 35 | fingerprint = response?.data?.meta?.fingerprint as string | null; 36 | } else { 37 | throw new Error(`Tron returned response ${response?.status} with statusText ${response?.statusText}.`); 38 | } 39 | } while (fingerprint); 40 | transactions = accumulatedTransactions; 41 | break; 42 | } catch (e) { 43 | if (i >= 3) { 44 | console.error( 45 | `Error getting Tron AccountTrcTransactions, transactions in timestamp range ${minTimestamp} to ${maxTimestamp} skipped.`, 46 | e 47 | ); 48 | return []; 49 | } else continue; 50 | } 51 | } 52 | return transactions; 53 | }; 54 | 55 | export const getTronLogs = async ( 56 | contractAddress: string, 57 | eventName: string, 58 | minBlockTimestamp: number, 59 | maxBlockTimestamp: number 60 | ) => { 61 | const tronRpc = `https://api.trongrid.io`; 62 | const tronLogs: any[] = []; 63 | let fingerprint = null; 64 | do { 65 | const url = `${tronRpc}/v1/contracts/${contractAddress}/events?event_name=${eventName}&min_block_timestamp=${ 66 | minBlockTimestamp * 1000 67 | }&max_block_timestamp=${maxBlockTimestamp * 1000}&limit=200${fingerprint ? `&fingerprint=${fingerprint}` : ""}`; 68 | const options = { 69 | method: "GET", 70 | headers: { "TRON-PRO-API-KEY": apiKeys[Math.floor(Math.random() * 3)], accept: "application/json" }, 71 | }; 72 | const response = await axios.get(url, options); 73 | if (response?.status !== 200) { 74 | throw new Error(`Tron returned response ${response?.status} with statusText ${response?.statusText}.`); 75 | } 76 | const body = response?.data; 77 | if (!body?.success) { 78 | throw new Error(`Error getting Tron events.`); 79 | } 80 | tronLogs.push(...body?.data); 81 | fingerprint = body?.meta?.fingerprint as string | null; 82 | } while (fingerprint); 83 | 84 | return tronLogs; 85 | }; 86 | 87 | export const tronGetLatestBlock = async () => { 88 | const response = await axios.post("https://api.trongrid.io/wallet/getblockbylatestnum", { num: 1 }); 89 | const { number, timestamp } = response?.data?.block?.[0]?.block_header?.raw_data; 90 | return { number: number, timestamp: Math.floor(timestamp / 1000) }; 91 | }; 92 | 93 | export const tronGetTimestampByBlockNumber = async (blockNumber: number) => { 94 | const response = await axios.post("https://api.trongrid.io/wallet/getblockbynum", { num: blockNumber }); 95 | const { timestamp } = response?.data?.block_header?.raw_data; 96 | return Math.floor(timestamp / 1000); 97 | }; 98 | -------------------------------------------------------------------------------- /src/server/cron.ts: -------------------------------------------------------------------------------- 1 | import { CronJob } from "cron"; 2 | import { runAllAdapters } from "./jobs/runAllAdapters"; 3 | import { runAggregateAllAdapters } from "./jobs/runAggregateAllAdapter"; 4 | import { runAdaptersFromTo } from "./jobs/runAdaptersFromTo"; 5 | import { handler as runWormhole } from "../handlers/runWormhole"; 6 | import { aggregateHourlyVolume } from "./jobs/aggregateHourlyVolume"; 7 | import { aggregateDailyVolume } from "./jobs/aggregateDailyVolume"; 8 | import { warmAllCaches } from "./jobs/warmCache"; 9 | import runLayerZero from "../handlers/runLayerZero"; 10 | import { querySql, sql } from "../utils/db"; 11 | import { runAggregateHistoricalByName } from "../utils/aggregate"; 12 | import { handler as runInterSoon } from "../handlers/runInterSoon"; 13 | import dayjs from "dayjs"; 14 | import runHyperlane from "../handlers/runHyperlane"; 15 | 16 | const createTimeout = (minutes: number) => 17 | new Promise((_, reject) => 18 | setTimeout(() => reject(new Error(`Operation timed out after ${minutes} minutes`)), minutes * 60 * 1000) 19 | ); 20 | 21 | const withTimeout = async (jobName: string, promise: Promise, timeoutMinutes: number) => { 22 | try { 23 | console.log(`[INFO] Starting job: ${jobName}`); 24 | 25 | const startTime = Date.now(); 26 | const result = await Promise.race([promise, createTimeout(timeoutMinutes)]); 27 | 28 | const duration = (Date.now() - startTime) / 1000; 29 | console.log(`[INFO] Job ${jobName} completed successfully in ${duration.toFixed(2)}s`); 30 | 31 | return result; 32 | } catch (error) { 33 | const errorMsg = error instanceof Error ? error.message : String(error); 34 | console.error(`[ERROR] Job ${jobName} failed: ${errorMsg}`); 35 | } 36 | }; 37 | 38 | const exit = () => { 39 | setTimeout(async () => { 40 | console.log("[INFO] Timeout! Shutting down. Bye bye!"); 41 | await sql.end(); 42 | await querySql.end(); 43 | process.exit(0); 44 | }, 1000 * 60 * 54); 45 | }; 46 | 47 | const runAfterDelay = async (jobName: string, delayMinutes: number, fn: () => Promise) => { 48 | setTimeout(async () => { 49 | try { 50 | await withTimeout(jobName, fn(), delayMinutes); 51 | } catch (error) { 52 | console.error(`[ERROR] Job ${jobName} failed:`, error); 53 | } 54 | }, delayMinutes * 60 * 1000); 55 | }; 56 | 57 | const runEvery = (jobName: string, minutes: number, fn: () => Promise) => { 58 | setInterval(async () => { 59 | try { 60 | await withTimeout(jobName, fn(), minutes); 61 | } catch (error) { 62 | console.error(`[ERROR] Job ${jobName} failed:`, error); 63 | } 64 | }, minutes * 60 * 1000); 65 | }; 66 | 67 | const cron = () => { 68 | if (process.env.NO_CRON) { 69 | return; 70 | } 71 | 72 | console.log(`[INFO] Starting cron service at ${new Date().toISOString()}`); 73 | 74 | withTimeout("warmCache", warmAllCaches(), 5); 75 | 76 | runAfterDelay("aggregateLayerZero", 5, () => 77 | runAggregateHistoricalByName(dayjs().subtract(2, "day").unix(), dayjs().unix(), "layerzero") 78 | ); 79 | 80 | runAfterDelay("aggregateAll", 5, runAggregateAllAdapters); 81 | runAfterDelay("aggregateHourly", 5, aggregateHourlyVolume); 82 | runAfterDelay("aggregateDaily", 5, aggregateDailyVolume); 83 | runAfterDelay("runAllAdapters", 10, runAllAdapters); 84 | runAfterDelay("runAdaptersFromTo", 50, runAdaptersFromTo); 85 | runEvery("runWormhole", 30, runWormhole); 86 | runEvery("runLayerZero", 30, runLayerZero); 87 | runEvery("runHyperlane", 30, runHyperlane); 88 | runEvery("runInterSoon", 30, runInterSoon); 89 | 90 | exit(); 91 | }; 92 | 93 | export default cron; 94 | -------------------------------------------------------------------------------- /src/server/health.ts: -------------------------------------------------------------------------------- 1 | import { cpuUsage } from "process"; 2 | import { cpus, freemem, totalmem, hostname, loadavg } from "os"; 3 | 4 | const CPU_HISTORY_HOURS = 24; 5 | const cpuHistory: Array<{ timestamp: string; usage: string }> = []; 6 | 7 | function formatBytes(bytes: number) { 8 | const gb = bytes / (1024 * 1024 * 1024); 9 | return `${gb.toFixed(2)} GB`; 10 | } 11 | 12 | function formatUptime(seconds: number) { 13 | const days = Math.floor(seconds / (24 * 60 * 60)); 14 | const hours = Math.floor((seconds % (24 * 60 * 60)) / (60 * 60)); 15 | const minutes = Math.floor((seconds % (60 * 60)) / 60); 16 | return `${days}d ${hours}h ${minutes}m`; 17 | } 18 | 19 | function updateCpuHistory() { 20 | const startUsage = cpuUsage(); 21 | 22 | setTimeout(() => { 23 | const endUsage = cpuUsage(startUsage); 24 | const totalUsage = endUsage.user + endUsage.system; 25 | const usagePercent = (totalUsage / 1000000 / 3600).toFixed(1); 26 | 27 | cpuHistory.push({ 28 | timestamp: new Date().toISOString(), 29 | usage: `${usagePercent}%`, 30 | }); 31 | 32 | if (cpuHistory.length > CPU_HISTORY_HOURS) { 33 | cpuHistory.shift(); 34 | } 35 | }, 1000); 36 | } 37 | 38 | export function startHealthMonitoring() { 39 | setInterval(updateCpuHistory, 3600000); 40 | updateCpuHistory(); 41 | } 42 | 43 | export function getHealthStatus() { 44 | const memoryTotal = totalmem(); 45 | const memoryFree = freemem(); 46 | const memoryUsed = memoryTotal - memoryFree; 47 | const memoryUsagePercent = ((memoryUsed / memoryTotal) * 100).toFixed(1); 48 | 49 | const [oneMin, fiveMin, fifteenMin] = loadavg(); 50 | const cpuCount = cpus().length; 51 | 52 | const health = { 53 | status: "OK", 54 | timestamp: new Date().toISOString(), 55 | server: { 56 | hostname: hostname(), 57 | uptime: formatUptime(process.uptime()), 58 | }, 59 | memory: { 60 | total: formatBytes(memoryTotal), 61 | used: formatBytes(memoryUsed), 62 | free: formatBytes(memoryFree), 63 | usage: `${memoryUsagePercent}%`, 64 | status: Number(memoryUsagePercent) > 90 ? "WARNING" : "OK", 65 | }, 66 | cpu: { 67 | cores: cpuCount, 68 | load: { 69 | "1min": ((oneMin / cpuCount) * 100).toFixed(1) + "%", 70 | "5min": ((fiveMin / cpuCount) * 100).toFixed(1) + "%", 71 | "15min": ((fifteenMin / cpuCount) * 100).toFixed(1) + "%", 72 | }, 73 | history: cpuHistory, 74 | status: oneMin / cpuCount > 0.8 ? "WARNING" : "OK", 75 | }, 76 | }; 77 | 78 | const statusCode = health.memory.status === "WARNING" || health.cpu.status === "WARNING" ? 207 : 200; 79 | 80 | return { health, statusCode }; 81 | } 82 | -------------------------------------------------------------------------------- /src/server/jobs/aggregateDailyVolume.ts: -------------------------------------------------------------------------------- 1 | import { sql } from "../../utils/db"; 2 | 3 | async function aggregateDailyVolume() { 4 | try { 5 | await sql` 6 | INSERT INTO bridges.daily_volume ( 7 | bridge_id, 8 | ts, 9 | total_deposited_usd, 10 | total_withdrawn_usd, 11 | total_deposit_txs, 12 | total_withdrawal_txs, 13 | chain 14 | ) 15 | SELECT 16 | ha.bridge_id, 17 | date_trunc('day', ha.ts) as ts, 18 | SUM(CAST(ha.total_deposited_usd AS NUMERIC)) as total_deposited_usd, 19 | SUM(CAST(ha.total_withdrawn_usd AS NUMERIC)) as total_withdrawn_usd, 20 | SUM(CAST(ha.total_deposit_txs AS INTEGER)) as total_deposit_txs, 21 | SUM(CAST(ha.total_withdrawal_txs AS INTEGER)) as total_withdrawal_txs, 22 | c.chain 23 | FROM bridges.hourly_aggregated ha 24 | JOIN bridges.config c ON ha.bridge_id = c.id 25 | GROUP BY 26 | ha.bridge_id, 27 | date_trunc('day', ha.ts), 28 | c.chain 29 | ON CONFLICT (bridge_id, ts, chain) DO UPDATE SET 30 | total_deposited_usd = EXCLUDED.total_deposited_usd, 31 | total_withdrawn_usd = EXCLUDED.total_withdrawn_usd, 32 | total_deposit_txs = EXCLUDED.total_deposit_txs, 33 | total_withdrawal_txs = EXCLUDED.total_withdrawal_txs; 34 | `; 35 | 36 | console.log("Daily volume aggregation completed successfully"); 37 | } catch (error) { 38 | console.error("Error during daily volume aggregation:", error); 39 | throw error; 40 | } 41 | } 42 | 43 | export { aggregateDailyVolume }; 44 | -------------------------------------------------------------------------------- /src/server/jobs/aggregateHourlyVolume.ts: -------------------------------------------------------------------------------- 1 | import { sql } from "../../utils/db"; 2 | 3 | async function aggregateHourlyVolume() { 4 | try { 5 | await sql` 6 | INSERT INTO bridges.hourly_volume ( 7 | bridge_id, 8 | ts, 9 | total_deposited_usd, 10 | total_withdrawn_usd, 11 | total_deposit_txs, 12 | total_withdrawal_txs, 13 | chain 14 | ) 15 | SELECT 16 | ha.bridge_id, 17 | ha.ts, 18 | CAST(ha.total_deposited_usd AS NUMERIC) as total_deposited_usd, 19 | CAST(ha.total_withdrawn_usd AS NUMERIC) as total_withdrawn_usd, 20 | CAST(ha.total_deposit_txs AS INTEGER) as total_deposit_txs, 21 | CAST(ha.total_withdrawal_txs AS INTEGER) as total_withdrawal_txs, 22 | c.chain 23 | FROM bridges.hourly_aggregated ha 24 | JOIN bridges.config c ON ha.bridge_id = c.id 25 | WHERE 26 | (ha.total_deposited_usd IS NOT NULL AND ha.total_deposited_usd::text ~ '^[0-9]+(\.[0-9]+)?$') 27 | AND (ha.total_withdrawn_usd IS NOT NULL AND ha.total_withdrawn_usd::text ~ '^[0-9]+(\.[0-9]+)?$') 28 | AND (ha.total_deposit_txs IS NOT NULL AND ha.total_deposit_txs::text ~ '^[0-9]+$') 29 | AND (ha.total_withdrawal_txs IS NOT NULL AND ha.total_withdrawal_txs::text ~ '^[0-9]+$') 30 | AND ha.ts >= NOW() - INTERVAL '30 days' 31 | ON CONFLICT (bridge_id, ts, chain) DO UPDATE SET 32 | total_deposited_usd = EXCLUDED.total_deposited_usd, 33 | total_withdrawn_usd = EXCLUDED.total_withdrawn_usd, 34 | total_deposit_txs = EXCLUDED.total_deposit_txs, 35 | total_withdrawal_txs = EXCLUDED.total_withdrawal_txs; 36 | `; 37 | 38 | console.log("Hourly volume aggregation completed successfully"); 39 | } catch (error) { 40 | console.error("Error during hourly volume aggregation:", error); 41 | throw error; 42 | } 43 | } 44 | 45 | export { aggregateHourlyVolume }; 46 | -------------------------------------------------------------------------------- /src/server/jobs/runAggregateAllAdapter.ts: -------------------------------------------------------------------------------- 1 | import { getCurrentUnixTimestamp } from "../../utils/date"; 2 | import { runAggregateDataHistoricalAllAdapters } from "../../utils/aggregate"; 3 | 4 | export const runAggregateAllAdapters = async () => { 5 | const currentTimestamp = getCurrentUnixTimestamp(); 6 | const startTimestamp = currentTimestamp - 86400; 7 | await runAggregateDataHistoricalAllAdapters(startTimestamp, currentTimestamp); 8 | }; 9 | -------------------------------------------------------------------------------- /src/server/jobs/runAllAdapters.ts: -------------------------------------------------------------------------------- 1 | import bridgeNetworks from "../../data/bridgeNetworkData"; 2 | import { sql } from "../../utils/db"; 3 | import { runAdapterToCurrentBlock } from "../../utils/adapter"; 4 | import { PromisePool } from "@supercharge/promise-pool"; 5 | 6 | export const runAllAdapters = async () => { 7 | const lastRecordedBlocks = await sql`SELECT jsonb_object_agg(bridge_id::text, subresult) as result 8 | FROM ( 9 | SELECT bridge_id, jsonb_build_object('startBlock', MIN(tx_block), 'endBlock', MAX(tx_block)) as subresult 10 | FROM bridges.transactions 11 | WHERE origin_chain IS NULL 12 | GROUP BY bridge_id 13 | ) subquery; 14 | `; 15 | try { 16 | console.log("Stored last recorded blocks"); 17 | } catch (e) { 18 | console.error("Failed to store last recorded blocks"); 19 | console.error(e); 20 | } 21 | const shuffledBridgeNetworks = bridgeNetworks.sort(() => Math.random() - 0.5); 22 | 23 | await PromisePool.withConcurrency(20) 24 | .for(shuffledBridgeNetworks) 25 | .process(async (adapter) => { 26 | try { 27 | await runAdapterToCurrentBlock(adapter, true, "upsert", lastRecordedBlocks[0].result); 28 | } catch (e) { 29 | console.error(`Failed to run adapter ${adapter.bridgeDbName}`); 30 | console.error(e); 31 | } 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /src/server/jobs/warmCache.ts: -------------------------------------------------------------------------------- 1 | import { warmCache, handlerRegistry, needsWarming } from "../../utils/cache"; 2 | 3 | const warmAllCaches = async () => { 4 | const cacheKeys = Array.from(handlerRegistry.keys()); 5 | 6 | for (const cacheKey of cacheKeys) { 7 | try { 8 | if (await needsWarming(cacheKey)) { 9 | await warmCache(cacheKey); 10 | await new Promise((resolve) => setTimeout(resolve, 1000)); 11 | } 12 | } catch (error) { 13 | console.error(`Failed to warm cache for key ${cacheKey}:`, error); 14 | } 15 | } 16 | }; 17 | 18 | export { warmAllCaches }; 19 | -------------------------------------------------------------------------------- /src/server/startCron.ts: -------------------------------------------------------------------------------- 1 | import cron from "./cron"; 2 | 3 | cron(); 4 | -------------------------------------------------------------------------------- /src/utils/cache.ts: -------------------------------------------------------------------------------- 1 | import Redis from "ioredis"; 2 | import hash from "object-hash"; 3 | 4 | const REDIS_URL = process.env.REDIS_URL; 5 | 6 | let redis: Redis; 7 | 8 | if (REDIS_URL) { 9 | redis = new Redis(REDIS_URL, { 10 | retryStrategy: (times) => { 11 | const delay = Math.min(times * 50, 2000); 12 | return delay; 13 | }, 14 | }); 15 | } 16 | 17 | interface APIEvent { 18 | pathParameters?: Record; 19 | queryStringParameters?: Record; 20 | body?: any; 21 | } 22 | 23 | export const handlerRegistry = new Map(); 24 | 25 | export const generateApiCacheKey = (event: APIEvent): string => { 26 | const eventToNormalize = { 27 | path: event.pathParameters || {}, 28 | query: event.queryStringParameters || {}, 29 | body: event.body || {}, 30 | }; 31 | 32 | return hash(eventToNormalize, { 33 | algorithm: "sha256", 34 | encoding: "hex", 35 | unorderedArrays: true, 36 | unorderedObjects: true, 37 | }).substring(0, 16); 38 | }; 39 | 40 | export const CACHE_WARM_THRESHOLD = 1000 * 60 * 10; 41 | export const DEFAULT_TTL = 60 * 70; // 70 minutes 42 | 43 | export const needsWarming = async (cacheKey: string): Promise => { 44 | const ttl = await redis.ttl(cacheKey); 45 | if (ttl === -1) return false; 46 | return true; 47 | }; 48 | 49 | export const warmCache = async (cacheKey: string): Promise => { 50 | const handler = handlerRegistry.get(cacheKey); 51 | if (!handler) { 52 | return; 53 | } 54 | try { 55 | const result = await handler(); 56 | const parsedBody = JSON.parse(result.body); 57 | await redis.set(cacheKey, JSON.stringify(parsedBody), "EX", DEFAULT_TTL); 58 | } catch (error) { 59 | throw error; 60 | } 61 | }; 62 | 63 | export const registerCacheHandler = (cacheKey: string, handler: Function) => { 64 | handlerRegistry.set(cacheKey, handler); 65 | }; 66 | 67 | export const getCacheKey = (...parts: (string | undefined)[]) => parts.filter(Boolean).join(":"); 68 | 69 | export const getCache = async (key: string): Promise => { 70 | const value = await redis.get(key); 71 | console.log("Cache HIT", key); 72 | return value ? JSON.parse(value) : null; 73 | }; 74 | 75 | export const setCache = async (key: string, value: any, ttl: number | null = DEFAULT_TTL): Promise => { 76 | if (ttl === null) { 77 | await redis.set(key, JSON.stringify(value)); 78 | } else { 79 | await redis.set(key, JSON.stringify(value), "EX", ttl); 80 | } 81 | }; 82 | 83 | export const deleteCache = async (key: string): Promise => { 84 | await redis.del(key); 85 | }; 86 | -------------------------------------------------------------------------------- /src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | export const PRICES_API = "https://coins.llama.fi/prices"; 2 | 3 | export const defaultConfidenceThreshold = 0.5; // for querying defillama prices 4 | 5 | // each chain should have number of blocks per approx. 1.5 or 2 hours. default is 400. 6 | export const maxBlocksToQueryByChain = { 7 | default: 300, 8 | ethereum: 300, 9 | polygon: 1000, 10 | fantom: 800, 11 | arbitrum: 3000, 12 | "zksync era": 5000, 13 | era: 4000, 14 | linea: 3000, 15 | manta: 800, 16 | blast: 2000, 17 | avalanche: 1000, 18 | avax: 3000, 19 | bsc: 2000, 20 | optimism: 3000, 21 | gnosis: 400, 22 | aurora: 5400, 23 | celo: 1200, 24 | klaytn: 6000, 25 | sui: 2400, // sui creates a checkpoint about every 3 seconds 26 | solana: 6000, 27 | taiko: 100, 28 | sonic: 10000, 29 | base: 3000, 30 | } as { [chain: string]: number }; 31 | 32 | // will be handled by the bridge adapter 33 | export const nonBlocksChains: string[] = []; 34 | 35 | /* 36 | // for slow adapters 37 | export const maxBlocksToQueryByChain = { 38 | default: 1600, 39 | ethereum: 1600, 40 | polygon: 8000, 41 | fantom: 20000, 42 | arbitrum: 40000, 43 | avalanche: 12000, 44 | avax: 12000, 45 | bsc: 8000, 46 | optimism: 40000, 47 | gnosis: 1200, 48 | aurora: 10000, 49 | celo: 3000, 50 | klaytn: 8000, 51 | } as { [chain: string]: number }; 52 | */ 53 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | export const secondsBetweenCalls = 60 * 60; 2 | export const secondsBetweenCallsExtra = secondsBetweenCalls * 1.5; // 1.5 to add some wiggle room 3 | export const secondsInDay = 60 * 60 * 24; 4 | export const secondsInWeek = secondsInDay * 7; 5 | export const secondsInHour = 60 * 60; 6 | export const HOUR = 3600; 7 | export const DAY = HOUR * 24; 8 | 9 | export function convertToUnixTimestamp(date: Date) { 10 | return Math.floor(date.getTime() / 1000); 11 | }; 12 | 13 | export function toUNIXTimestamp(ms: number) { 14 | return Math.round(ms / 1000); 15 | } 16 | 17 | export function getCurrentUnixTimestamp() { 18 | return toUNIXTimestamp(Date.now()); 19 | } 20 | 21 | export function getTimestampAtStartOfDay(timestamp: number) { 22 | const dt = new Date(timestamp * 1000); 23 | dt.setHours(0, 0, 0, 0); 24 | return toUNIXTimestamp(dt.getTime() - dt.getTimezoneOffset() * 6e4); 25 | } 26 | 27 | export const getTimestampAtStartOfDayUTC = (timestamp: number) => { 28 | const date = new Date(timestamp * 1000); 29 | var date_utc = Date.UTC( 30 | date.getUTCFullYear(), 31 | date.getUTCMonth(), 32 | date.getUTCDate(), 33 | date.getUTCHours(), 34 | date.getUTCMinutes(), 35 | date.getUTCSeconds() 36 | ); 37 | var startOfDay = Number(new Date(date_utc)); 38 | var timestamp = startOfDay / 1000; 39 | return Math.floor(timestamp / 86400) * 86400; 40 | }; 41 | 42 | export const getTimestampAtStartOfNextDayUTC = (timestamp: number) => { 43 | const date = new Date(timestamp * 1000); 44 | var date_utc = Date.UTC( 45 | date.getUTCFullYear(), 46 | date.getUTCMonth(), 47 | date.getUTCDate() + 1 48 | ); 49 | return date_utc / 1000; 50 | }; 51 | 52 | export function calcIsNewDay(timestamp: number) { 53 | return timestamp % 86400 === 0; 54 | } 55 | 56 | export const getTimestampAtStartOfHour = (timestamp: number) => { 57 | const date = new Date(timestamp * 1000); 58 | var date_utc = Date.UTC( 59 | date.getUTCFullYear(), 60 | date.getUTCMonth(), 61 | date.getUTCDate(), 62 | date.getUTCHours(), 63 | date.getUTCMinutes(), 64 | date.getUTCSeconds() 65 | ); 66 | var startOfDay = Number(new Date(date_utc)); 67 | var timestamp = startOfDay / 1000; 68 | return Math.floor(timestamp / 3600) * 3600; 69 | }; 70 | 71 | export const getTimestampAtStartOfMonth = (timestamp: number) => { 72 | const date = new Date(timestamp * 1000); 73 | const firstDay = Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1); 74 | return firstDay.valueOf() / 1000; 75 | }; 76 | 77 | export const getTimestampAtStartOfNextMonth = (timestamp: number) => { 78 | const date = new Date(timestamp * 1000); 79 | const firstDay = Date.UTC(date.getUTCFullYear(), date.getUTCMonth() + 1, 1); 80 | return firstDay.valueOf() / 1000; 81 | }; 82 | 83 | export function getDay(timestamp: number | undefined): string { 84 | if (timestamp == undefined) { 85 | return "none"; 86 | } 87 | var dt = new Date(timestamp * 1000); 88 | return `${dt.getUTCDate()}-${dt.getUTCMonth()}-${dt.getUTCFullYear()}`; 89 | } 90 | 91 | export function getClosestDayStartTimestamp(timestamp: number) { 92 | const dt = new Date(timestamp * 1000); 93 | dt.setUTCHours(0, 0, 0, 0); 94 | const prevDayTimestamp = toUNIXTimestamp(dt.getTime()); 95 | dt.setUTCHours(24); 96 | const nextDayTimestamp = toUNIXTimestamp(dt.getTime()); 97 | if ( 98 | Math.abs(prevDayTimestamp - timestamp) < 99 | Math.abs(nextDayTimestamp - timestamp) 100 | ) { 101 | return prevDayTimestamp; 102 | } else { 103 | return nextDayTimestamp; 104 | } 105 | } 106 | 107 | function pad(s: number) { 108 | return s < 10 ? "0" + s : s; 109 | } 110 | 111 | export function formatTimestampAsDate(timestamp: string) { 112 | const date = new Date(Number(timestamp) * 1000); 113 | return `${pad(date.getDate())}/${pad( 114 | date.getMonth() + 1 115 | )}/${date.getFullYear()}`; 116 | } 117 | -------------------------------------------------------------------------------- /src/utils/db.js: -------------------------------------------------------------------------------- 1 | import postgres from "postgres"; 2 | import dotenv from "dotenv"; 3 | 4 | dotenv.config(); 5 | 6 | const connectionString = 7 | process.env.DB_URL ?? 8 | `postgresql://${process.env.PSQL_USERNAME}:${process.env.PSQL_PW}@${process.env.PSQL_URL}:9004/cle37p03g00dhd6lff4ch90qw`; 9 | 10 | const sql = postgres(connectionString, { 11 | idle_timeout: 20, 12 | max_lifetime: 60 * 30, 13 | max: 7, 14 | }); 15 | 16 | const querySql = postgres(connectionString, { 17 | idle_timeout: 20, 18 | max_lifetime: 60 * 30, 19 | max: 4, 20 | }); 21 | 22 | export { sql, querySql }; 23 | -------------------------------------------------------------------------------- /src/utils/discord.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | 3 | export async function sendDiscordText(message: string): Promise { 4 | try { 5 | if (message.length > 2000) { 6 | console.warn("Discord message exceeds 2000 characters. Truncating..."); 7 | message = message.substring(0, 1997) + "..."; 8 | } 9 | 10 | const res = await fetch(process.env.DISCORD_WEBHOOK as string, { 11 | method: "POST", 12 | headers: { 13 | "Content-Type": "application/json", 14 | }, 15 | body: JSON.stringify({ content: message }), 16 | }); 17 | 18 | if (!res.ok) { 19 | const errorText = await res.text(); 20 | console.error(`Failed to send message to Discord: Status ${res.status} ${res.statusText}`); 21 | console.error("Error details:", errorText); 22 | } 23 | 24 | console.log("Message sent to Discord successfully"); 25 | } catch (error: any) { 26 | console.error("Failed to send message to Discord:", error); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/getRecordClosestToTimestamp.ts: -------------------------------------------------------------------------------- 1 | import { getHourlyBridgeVolume, getDailyBridgeVolume } from "./bridgeVolume"; 2 | 3 | export default async function getAggregatedDataClosestToTimestamp( 4 | timestamp: number, 5 | searchWidth: number, 6 | hourly?: boolean, 7 | chain?: string, 8 | bridgeNetworkId?: number, 9 | ) { 10 | const volumeFn = hourly 11 | ? getHourlyBridgeVolume 12 | : getDailyBridgeVolume; 13 | const aggregatedData = await volumeFn( 14 | timestamp - searchWidth, 15 | timestamp + searchWidth, 16 | chain, 17 | bridgeNetworkId 18 | ); 19 | 20 | if (!aggregatedData.length) { 21 | return {}; 22 | } 23 | 24 | let closestRecord = aggregatedData[0]; 25 | for (const record of aggregatedData) { 26 | const closestRecordTs = closestRecord.date; 27 | const ts = record.date; 28 | if (Math.abs(ts - timestamp) < Math.abs(closestRecordTs - timestamp)) { 29 | closestRecord = record; 30 | } 31 | } 32 | return closestRecord; 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/insertConfigRows.ts: -------------------------------------------------------------------------------- 1 | 2 | import { insertConfigEntriesForAdapter } from "./adapter"; 3 | import adapters from "../adapters"; 4 | import { isAsyncAdapter } from "../utils/adapter"; 5 | 6 | async function insertConfigRows(bridgeDbName: string) { 7 | let adapter = adapters[bridgeDbName]; 8 | adapter = isAsyncAdapter(adapter) ? await adapter.build() : adapter; 9 | await insertConfigEntriesForAdapter(adapter, bridgeDbName) 10 | } 11 | 12 | insertConfigRows("allbridge") 13 | -------------------------------------------------------------------------------- /src/utils/insertRecordedBlocks.ts: -------------------------------------------------------------------------------- 1 | import recordedBlocksRecord from "./recordedBlocks.json"; 2 | import adapters from "../adapters"; 3 | import { isAsyncAdapter } from "../utils/adapter"; 4 | import { lookupBlock } from "@defillama/sdk/build/util"; 5 | import { Chain } from "@defillama/sdk/build/general"; 6 | import bridgeNetworks from "../data/bridgeNetworkData"; 7 | import { importBridgeNetwork } from "../data/importBridgeNetwork"; 8 | const FileSystem = require("fs"); 9 | 10 | const insertRecordedBlocks = async (adapterName: string, startTimestamp: number, endTimestamp: number) => { 11 | let recordedBlocks = recordedBlocksRecord as { [adapterChain: string]: { startBlock: number; endBlock: number } }; 12 | let adapter = adapters[adapterName]; 13 | adapter = isAsyncAdapter(adapter) ? await adapter.build() : adapter; 14 | if (!adapter) { 15 | throw new Error(`Adapter for ${adapterName} not found, check it is exported correctly.`); 16 | } 17 | const bridgeNetwork = importBridgeNetwork(adapterName)!; 18 | const blockPromises = Promise.all( 19 | Object.keys(adapter).map(async (chain) => { 20 | const chainContractsAreOn = bridgeNetwork.chainMapping?.[chain as Chain] 21 | ? bridgeNetwork.chainMapping?.[chain as Chain] 22 | : chain; 23 | if (recordedBlocks[`${adapterName}:${chain}`]) { 24 | console.info(`Adapter ${adapterName} has recorded blocks entry on chain ${chain}, skipping.`); 25 | } else { 26 | const startBlock = (await lookupBlock(startTimestamp, { chain: chainContractsAreOn as Chain })).block; 27 | const endBlock = (await lookupBlock(endTimestamp, { chain: chainContractsAreOn as Chain })).block; 28 | if (!(startBlock && endBlock)) { 29 | throw new Error(`Could not get blocks for chain ${chain}.`); 30 | } 31 | recordedBlocks[`${adapterName}:${chain}`] = { 32 | startBlock: startBlock, 33 | endBlock: endBlock, 34 | }; 35 | } 36 | }) 37 | ); 38 | await blockPromises; 39 | }; 40 | 41 | insertRecordedBlocks("polynetwork", 1661904000, 1668618000); 42 | -------------------------------------------------------------------------------- /src/utils/lambda-response.ts: -------------------------------------------------------------------------------- 1 | interface IJSON { 2 | [key: string]: any; 3 | } 4 | 5 | interface IResponseOptions { 6 | body: IJSON; 7 | statusCode: number; 8 | allowCORS?: boolean; 9 | cacheTTL?: number; 10 | headers?: Headers; 11 | } 12 | 13 | export interface IResponse { 14 | statusCode: number; 15 | body: string; 16 | headers?: { 17 | [key: string]: any; 18 | }; 19 | } 20 | 21 | export interface Headers { 22 | [name: string]: string; 23 | } 24 | 25 | function lambdaResponse({ 26 | body, 27 | statusCode, 28 | cacheTTL, 29 | allowCORS = false, 30 | headers, 31 | }: IResponseOptions) { 32 | const response: IResponse = { 33 | statusCode, 34 | body: JSON.stringify(body), 35 | }; 36 | response.headers = { 37 | "Content-Type": "application/json", 38 | ...headers, 39 | }; 40 | 41 | if ( 42 | allowCORS && 43 | response.headers["Access-Control-Allow-Origin"] === undefined 44 | ) { 45 | response.headers["Access-Control-Allow-Origin"] = "*"; 46 | } 47 | if (cacheTTL !== undefined) { 48 | response.headers["Cache-Control"] = `max-age=${cacheTTL}`; 49 | } 50 | 51 | response.headers["Content-Type"] = "application/json"; 52 | 53 | return response; 54 | } 55 | 56 | export function errorResponse(body: { message: string }) { 57 | const errorParams = { 58 | statusCode: 400, 59 | allowCORS: true, 60 | }; 61 | return lambdaResponse({ 62 | body, 63 | ...errorParams, 64 | }); 65 | } 66 | 67 | // TTL must be in seconds 68 | export function successResponse( 69 | json: IJSON, 70 | cacheTTL?: number, 71 | headers?: Headers 72 | ) { 73 | return lambdaResponse({ 74 | body: json, 75 | statusCode: 200, 76 | allowCORS: true, 77 | cacheTTL, 78 | headers, 79 | }); 80 | } 81 | 82 | export function cache20MinResponse(json: IJSON) { 83 | const date = new Date(); 84 | date.setMinutes(20); 85 | if (date < new Date()) { 86 | // we are past the :20 mark, roll over to next hour 87 | date.setHours(date.getHours() + 1); 88 | } 89 | 90 | return lambdaResponse({ 91 | body: json, 92 | statusCode: 200, 93 | allowCORS: true, 94 | headers: { 95 | Expires: date.toUTCString(), 96 | }, 97 | }); 98 | } 99 | 100 | export function corsSuccessResponse(json: IJSON) { 101 | return lambdaResponse({ 102 | body: json, 103 | statusCode: 200, 104 | allowCORS: true, 105 | }); 106 | } 107 | 108 | export function getBody(response: string | void | IResponse) { 109 | if (response instanceof Object) { 110 | return JSON.parse(response.body); 111 | } 112 | throw new Error( 113 | "response is not of the correct type, this should never happen" 114 | ); 115 | } 116 | 117 | export function credentialsCorsHeaders() { 118 | return { 119 | "Access-Control-Allow-Origin": "*", 120 | "Access-Control-Allow-Credentials": "true", 121 | }; 122 | } 123 | -------------------------------------------------------------------------------- /src/utils/prices.ts: -------------------------------------------------------------------------------- 1 | import { PRICES_API } from "./constants"; 2 | const axios = require("axios"); 3 | const retry = require("async-retry"); 4 | 5 | const maxNumberOfPrices = 150; 6 | 7 | export const getSingleLlamaPrice = async ( 8 | chain: string, 9 | token: string, 10 | timestamp?: number, 11 | confidenceThreshold?: number 12 | ) => { 13 | const url = timestamp 14 | ? PRICES_API + `/historical/${timestamp}/${chain}:${token}` 15 | : PRICES_API + `/current/${chain}:${token}`; 16 | const res = await retry(async (_bail: any) => await axios.get(url)); 17 | const price = res?.data?.coins?.[`${chain}:${token}`]; 18 | if (!confidenceThreshold || !price || price.confidence > confidenceThreshold) { 19 | return price; 20 | } 21 | return null; 22 | }; 23 | 24 | const NAME_MAPPING: Record = { 25 | "b2-mainnet": "bsquared", 26 | xdai: "gnosis", 27 | }; 28 | 29 | export const getLlamaPrices = async (tokens: string[], timestamp?: number): Promise> => { 30 | const finalPrices: { [key: string]: any } = {}; 31 | let remainingTokens = tokens.map((token) => { 32 | const [prefix, id] = token.split(":"); 33 | const mappedPrefix = NAME_MAPPING[prefix] || prefix; 34 | return `${mappedPrefix}:${id}`; 35 | }); 36 | 37 | while (remainingTokens.length > 0) { 38 | const url = timestamp 39 | ? `${PRICES_API}/historical/${timestamp}/${remainingTokens.slice(0, maxNumberOfPrices).join(",")}` 40 | : `${PRICES_API}/current/${remainingTokens.slice(0, maxNumberOfPrices).join(",")}`; 41 | const res = await retry(async () => await axios.get(url)); 42 | const prices = res?.data?.coins; 43 | Object.assign(finalPrices, prices); 44 | remainingTokens = remainingTokens.slice(maxNumberOfPrices); 45 | } 46 | 47 | return Object.fromEntries( 48 | Object.entries(finalPrices).map(([key, value]) => { 49 | const [prefix, id] = key.split(":"); 50 | const originalPrefix = Object.entries(NAME_MAPPING).find(([, mappedName]) => mappedName === prefix)?.[0]; 51 | return [`${originalPrefix || prefix}:${id}`, value]; 52 | }) 53 | ); 54 | }; 55 | -------------------------------------------------------------------------------- /src/utils/provider.ts: -------------------------------------------------------------------------------- 1 | // import { getClient } from "../helpers/sui"; 2 | import { getProvider as getLlamaProvider } from "@defillama/sdk"; 3 | import { getConnection } from "../helpers/solana"; 4 | 5 | export function getProvider(chain: string) { 6 | if (chain === "sui") { 7 | // return getClient(); 8 | } else if (chain === "solana") { 9 | return getConnection(); 10 | } 11 | 12 | return getLlamaProvider(chain); 13 | } 14 | -------------------------------------------------------------------------------- /src/utils/s3.ts: -------------------------------------------------------------------------------- 1 | import { S3Client, PutObjectCommand, ObjectCannedACL } from "@aws-sdk/client-s3"; 2 | import type { Readable } from "stream"; 3 | 4 | const datasetBucket = "llama-bridges-data"; 5 | const s3Client = new S3Client({}); 6 | 7 | function next21Minutedate() { 8 | const dt = new Date(); 9 | dt.setHours(dt.getHours() + 1); 10 | dt.setMinutes(21); 11 | return dt; 12 | } 13 | 14 | export async function store( 15 | filename: string, 16 | body: string | Readable | Buffer, 17 | hourlyCache = false, 18 | compressed = true 19 | ) { 20 | const params = { 21 | Bucket: datasetBucket, 22 | Key: filename, 23 | Body: body, 24 | ACL: ObjectCannedACL.public_read, 25 | ...(hourlyCache && { 26 | Expires: next21Minutedate(), 27 | ...(compressed && { 28 | ContentEncoding: "br", 29 | }), 30 | ContentType: "application/json", 31 | }), 32 | }; 33 | 34 | const command = new PutObjectCommand(params); 35 | await s3Client.send(command); 36 | } 37 | 38 | export async function storeDataset(filename: string, body: string) { 39 | const params = { 40 | Bucket: datasetBucket, 41 | Key: `temp/${filename}`, 42 | Body: body, 43 | ACL: ObjectCannedACL.public_read, 44 | ContentType: "text/csv", 45 | }; 46 | 47 | const command = new PutObjectCommand(params); 48 | await s3Client.send(command); 49 | } 50 | -------------------------------------------------------------------------------- /src/utils/testDailyVolume.ts: -------------------------------------------------------------------------------- 1 | import bridgeNetworkData from "../data/bridgeNetworkData"; 2 | import { aggregateDailyVolume } from "../server/jobs/aggregateDailyVolume"; 3 | import { aggregateHourlyVolume } from "../server/jobs/aggregateHourlyVolume"; 4 | import { getDailyBridgeVolume } from "./bridgeVolume"; 5 | 6 | const startTs = Number(process.argv[2]); 7 | const endTs = Number(process.argv[3]); 8 | const bridgeName = process.argv[4]; 9 | 10 | const main = async () => { 11 | await aggregateHourlyVolume(); 12 | await aggregateDailyVolume(); 13 | const data = await getDailyBridgeVolume( 14 | startTs, 15 | endTs, 16 | undefined, 17 | bridgeNetworkData.find(({ bridgeDbName }) => bridgeDbName === bridgeName)?.id 18 | ); 19 | console.log(data); 20 | process.exit(); 21 | }; 22 | 23 | main(); 24 | -------------------------------------------------------------------------------- /src/utils/testadapter.ts: -------------------------------------------------------------------------------- 1 | import { runAllAdaptersToCurrentBlock, runAdapterHistorical } from "./adapter"; 2 | 3 | runAllAdaptersToCurrentBlock(false, "ignore") 4 | 5 | // runAdapterHistorical(true, 15624531, 15667814, 1, "polygon", "ignore") -------------------------------------------------------------------------------- /src/utils/testaggregate.ts: -------------------------------------------------------------------------------- 1 | import bridgeNetworkData from "../data/bridgeNetworkData"; 2 | import { runAggregateDataHistorical } from "./aggregate"; 3 | import PromisePool from "@supercharge/promise-pool"; 4 | const startTs = Number(process.argv[2]); 5 | const endTs = Number(process.argv[3]); 6 | const bridgeName = process.argv[4]; 7 | const chain = process.argv[5]; 8 | 9 | async function aggregateHistorical( 10 | startTimestamp: number, 11 | endTimestamp: number, 12 | bridgeDbName: string, 13 | restrictChain?: string[] 14 | ) { 15 | if (!startTimestamp || !endTimestamp || !bridgeDbName) { 16 | console.error( 17 | "Missing parameters, please provide startTimestamp, endTimestamp and bridgeDbName. \nExample: npm run aggregate 1704690402 1704949602 arbitrum" 18 | ); 19 | process.exit(); 20 | } 21 | 22 | const adapter = bridgeNetworkData.find((x) => x.bridgeDbName === bridgeDbName); 23 | if (!adapter) throw new Error("Invalid adapter"); 24 | console.log(`Found ${bridgeDbName}`); 25 | if (restrictChain) { 26 | restrictChain.forEach(async (chain: string) => { 27 | await runAggregateDataHistorical(startTimestamp, endTimestamp, adapter.id, true, chain); 28 | }); 29 | } else { 30 | await runAggregateDataHistorical(startTimestamp, endTimestamp, adapter.id, true, restrictChain); 31 | } 32 | } 33 | const runAllAdaptersHistorical = async (startTimestamp: number, endTimestamp: number) => { 34 | await PromisePool.withConcurrency(5) 35 | .for(bridgeNetworkData) 36 | .process(async (adapter) => { 37 | await aggregateHistorical(startTimestamp, endTimestamp, adapter.bridgeDbName); 38 | }); 39 | }; 40 | 41 | if (bridgeName) { 42 | aggregateHistorical(startTs, endTs, bridgeName, chain ? [chain] : undefined); 43 | } else { 44 | runAllAdaptersHistorical(startTs, endTs); 45 | } 46 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | 3 | type EventKeys = "blockNumber" | "txHash" | "from" | "to" | "token" | "amount"; 4 | 5 | export type EventData = { 6 | blockNumber: number; 7 | txHash: string; 8 | from: string; 9 | to: string; 10 | token: string; 11 | amount: ethers.BigNumber; 12 | isDeposit: boolean; 13 | chain?: string; 14 | chainOverride?: string; // used to insert tx using bridgeID from same bridgeNetwork but a different chain 15 | isUSDVolume?: boolean; // used to insert tx without specifying any token, only a USD value 16 | txsCountedAs?: number; // used to insert tx and have it count as multiple txs (only affects transaction counts in hourly/daily aggregated entries) 17 | timestamp?: number; // timestamp of the block if provided 18 | }; 19 | 20 | export type EventKeyMapping = { 21 | [key in EventKeys]?: string; 22 | }; 23 | 24 | export type RecordedBlocksFromAWS = { 25 | [adapterDbNameChain: string]: { 26 | startBlock: number; 27 | endBlock: number; 28 | }; 29 | }; 30 | 31 | export type RecordedBlocks = { 32 | startBlock: number; 33 | endBlock: number; 34 | }; 35 | -------------------------------------------------------------------------------- /src/utils/wrap.ts: -------------------------------------------------------------------------------- 1 | import { IResponse, credentialsCorsHeaders } from "./lambda-response"; 2 | 3 | type Event = 4 | | AWSLambda.APIGatewayEvent 5 | | { 6 | source: string; 7 | }; 8 | 9 | function wrap( 10 | lambdaFunc: (event: AWSLambda.APIGatewayEvent) => Promise 11 | ): ( 12 | event: Event, 13 | context?: any, 14 | callback?: any 15 | ) => Promise | void { 16 | const handler = async (event: Event) => { 17 | if ("source" in event) { 18 | if (event.source === "serverless-plugin-warmup") { 19 | return "pinged"; 20 | } 21 | throw new Error("Unexpected source"); 22 | } 23 | return lambdaFunc(event).then((response) => ({ 24 | ...response, 25 | headers: { 26 | ...response.headers, 27 | ...credentialsCorsHeaders(), 28 | }, 29 | })); 30 | }; 31 | return handler; 32 | } 33 | 34 | export default wrap; 35 | 36 | export function wrapScheduledLambda( 37 | lambdaFunc: (event: any, context: AWSLambda.Context) => Promise 38 | ): ( 39 | event: void, 40 | context?: any, 41 | callback?: any 42 | ) => Promise | void { 43 | return lambdaFunc; 44 | } 45 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | }, 7 | "compilerOptions": { 8 | "target": "es2019", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "ESNext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "noEmit": false, 18 | "jsx": "preserve", 19 | "allowJs": true, 20 | "noImplicitThis": false, 21 | "noUnusedLocals": false, 22 | "noUnusedParameters": true, 23 | "lib": ["ES2019", "DOM"], 24 | "outDir": ".webpack", 25 | "rootDir": "./src", 26 | "sourceMap": true 27 | }, 28 | "include": ["src"] 29 | } -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "vite"; 2 | import path from "path"; 3 | 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: { 8 | index: path.resolve(__dirname, "src/server/index.ts"), 9 | startCron: path.resolve(__dirname, "src/server/startCron.ts"), 10 | }, 11 | formats: ["cjs"], 12 | fileName: (format, entryName) => `${entryName}.js`, 13 | }, 14 | rollupOptions: { 15 | external: ["fastify", "@fastify/cors", ...Object.keys(require("./package.json").dependencies)], 16 | }, 17 | ssr: true, 18 | target: "node16", 19 | outDir: "dist", 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const slsw = require("serverless-webpack"); 2 | const path = require("path"); 3 | const TerserPlugin = require("terser-webpack-plugin"); 4 | const nodeExternals = require("webpack-node-externals"); 5 | 6 | module.exports = { 7 | entry: slsw.lib.entries, 8 | output: { 9 | libraryTarget: "commonjs", 10 | filename: "[name].js", 11 | path: path.join(__dirname, ".webpack"), 12 | }, 13 | target: "node", 14 | mode: slsw.lib.webpack.isLocal ? "development" : "production", 15 | externals: [nodeExternals()], 16 | optimization: { 17 | minimize: true, 18 | minimizer: [new TerserPlugin({ terserOptions: { keep_classnames: true } })], 19 | splitChunks: { 20 | chunks: "all", 21 | minSize: 10000, 22 | maxSize: 250000, 23 | }, 24 | }, 25 | module: { 26 | rules: [ 27 | { 28 | test: /\.ts$/, 29 | use: "ts-loader", 30 | include: path.resolve(__dirname, "src"), 31 | exclude: /node_modules/, 32 | }, 33 | { 34 | test: /\.js$/, 35 | include: __dirname, 36 | exclude: /node_modules/, 37 | use: { 38 | loader: "babel-loader", 39 | }, 40 | }, 41 | { 42 | test: /\.mjs$/, 43 | resolve: { mainFields: ["default"] }, 44 | }, 45 | ], 46 | }, 47 | resolve: { 48 | mainFields: ["main", "browser"], 49 | symlinks: false, 50 | extensions: [".ts", ".js", ".json"], 51 | alias: { 52 | "bignumber.js$": "bignumber.js/bignumber.js", 53 | "node-fetch$": "node-fetch/lib/index.js", 54 | }, 55 | }, 56 | }; 57 | --------------------------------------------------------------------------------