Skip to content

Commit

Permalink
Merge pull request #287 from BootNodeDev/hyperlane
Browse files Browse the repository at this point in the history
feat(adapters): Add Hyperlane
  • Loading branch information
vrtnd authored Nov 1, 2024
2 parents 5add13c + a9a073d commit d14de44
Show file tree
Hide file tree
Showing 13 changed files with 304 additions and 19 deletions.
17 changes: 13 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"name": "bridges-server",
"scripts": {
"deploy:prod": "sls deploy --stage prod",
"serve": "sls offline start",
Expand All @@ -13,6 +14,7 @@
"devDependencies": {
"@types/async-retry": "^1.4.8",
"@types/aws-lambda": "^8.10.101",
"@types/js-yaml": "^4.0.9",
"@types/node": "^18.6.4",
"@types/node-fetch": "^2.6.2",
"@types/retry": "^0.12.5",
Expand All @@ -26,7 +28,7 @@
"dependencies": {
"@aws-sdk/client-lambda": "^3.637.0",
"@aws-sdk/client-s3": "^3.637.0",
"@defillama/sdk": "^5.0.80",
"@defillama/sdk": "^5.0.94",
"@graphql-typed-document-node/core": "^3.2.0",
"@solana/web3.js": "^1.87.3",
"async-retry": "^1.3.1",
Expand Down
161 changes: 161 additions & 0 deletions src/adapters/hyperlane/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
import { ethers } from "ethers";
import { getProvider, setProvider } from "@defillama/sdk";
import { LlamaProvider } from "@defillama/sdk/build/util/LlamaProvider";
import providerList from "@defillama/sdk/build/providers.json";
import { Chain } from "@defillama/sdk/build/general";

import { BridgeAdapter, PartialContractEventParams } from "../../helpers/bridgeAdapter.type";
import { getTxDataFromEVMEventLogs } from "../../helpers/processTransactions";

import * as yaml from 'js-yaml';

const baseUri = "https://raw.githubusercontent.com/hyperlane-xyz/hyperlane-registry/main";

let metadata: Record<string, any>;
let addresses: Record<string, Record<string, string>>;
const chainMapping: Record<string, string> = {};

async function setUp(): Promise<void> {
metadata =
(await fetch(`${baseUri}/chains/metadata.yaml`)
.then((r) => r.text())
.then((t) => yaml.load(t)) as Record<string, any>);
addresses =
(await fetch(`${baseUri}/chains/addresses.yaml`)
.then((r) => r.text())
.then((t) => yaml.load(t)) as Record<string, Record<string, string>>);

for (const [, chain] of Object.entries(metadata)) {
if (chain.isTestnet) continue;
if (chain.protocol != "ethereum") continue;

const provider = getProvider(chain.name);
if (provider === null) {
for (const p in providerList) {
const data = (providerList as any)[p];
if (data.chainId == chain.chainId) {
chainMapping[chain.name] = p;
setProvider(chain.name, getProvider(p));
break;
}
}
}
}
// const missing = [];
// for (const key in metadata) {
// const chain = metadata[key];
// if (chain.isTestnet) continue;
// if (chain.protocol != "ethereum") continue;

// const provider = getProvider(chain.name);
// if (provider === null) missing.push(chain);
// }
// console.log(missing.length);
// console.log(missing);
};

function bytes32ToAddress(bytes32: string) {
return ethers.utils.getAddress('0x' + bytes32.slice(26));
}

function constructParams(chain: string) {
let eventParams = [] as PartialContractEventParams[];
const mailboxAddress = ethers.utils.getAddress(addresses[chain].mailbox);

function buildFilter(eventName: string): (provider: LlamaProvider, iface: ethers.utils.Interface, txHash: string) => Promise<boolean> {
return async (provider, iface, txHash) => {
const txReceipt = await provider.getTransactionReceipt(txHash);
if (!txReceipt) return true;

let toFilter = true;
txReceipt.logs.map((log: ethers.providers.Log) => {
let parsed;
try {
parsed = iface.parseLog(log);
} catch { return; }
// console.log(parsed);
// console.log(ethers.utils.getAddress(log.address));
if (ethers.utils.getAddress(log.address) === mailboxAddress && parsed.name === eventName) {
toFilter = false;
}
});
return toFilter;
}
}

const commonParams = {
target: null,
logKeys: {
blockNumber: "blockNumber",
txHash: "transactionHash",
from: "address",
token: "address",
},
argKeys: {
to: "recipient",
amount: "amount",
},
argGetters: {
to: (log: any) => { bytes32ToAddress(log.recipient); }
},
};
const depositParams: PartialContractEventParams = {
...commonParams,
topic: "SentTransferRemote(uint32,bytes32,uint256)",
abi: [
"event SentTransferRemote(uint32 indexed destination, bytes32 indexed recipient, uint256 amount)",
"event Dispatch(address indexed sender, uint32 indexed destination, bytes32 indexed recipient, bytes message)",
],
isDeposit: true,
filter: {
custom: buildFilter("Dispatch")
},
};

const withdrawParams: PartialContractEventParams = {
...commonParams,
topic: "ReceivedTransferRemote(uint32,bytes32,uint256)",
abi: [
"event ReceivedTransferRemote(uint32 indexed origin, bytes32 indexed recipient, uint256 amount)",
"event Process(uint32 indexed origin, bytes32 indexed sender, address indexed recipient)",
],
isDeposit: false,
filter: {
custom: buildFilter("Process")
},
};

eventParams.push(depositParams, withdrawParams);

return async (fromBlock: number, toBlock: number) => {
return await getTxDataFromEVMEventLogs("hyperlane", (chainMapping[chain] || chain) as Chain, fromBlock, toBlock, eventParams);
}
}

const excludedChains = [
"cheesechain", // TODO: not available in defillama sdk providerList, can be added manually
"lumia", // TODO: not available in defillama sdk providerList, can be added manually
];

async function build(): Promise<BridgeAdapter> {
await setUp();

const adapter: BridgeAdapter = {
}

for (const key in metadata) {
const chain = metadata[key];
if (chain.isTestnet) continue;
if (chain.protocol != "ethereum") continue;

if (!addresses.hasOwnProperty(key)) continue;
if (excludedChains.includes(key)) continue;

const adapterKey = chainMapping[key] || key;
adapter[adapterKey] = constructParams(key);
}

return adapter;
}

export default { isAsync: true, build };
6 changes: 4 additions & 2 deletions src/adapters/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BridgeAdapter } from "../helpers/bridgeAdapter.type";
import { BridgeAdapter, AsyncBridgeAdapter } from "../helpers/bridgeAdapter.type";
import polygon from "./polygon";
import synapse from "./synapse";
import hop from "./hop";
Expand Down Expand Up @@ -71,6 +71,7 @@ import mint from "./mint";
import suibridge from "./suibridge";
import retrobridge from "./retrobridge"
import layerswap from "./layerswap"
import hyperlane from "./hyperlane";

export default {
polygon,
Expand Down Expand Up @@ -145,6 +146,7 @@ export default {
suibridge,
retrobridge,
layerswap,
hyperlane,
} as {
[bridge: string]: BridgeAdapter;
[bridge: string]: BridgeAdapter | AsyncBridgeAdapter;
};
4 changes: 3 additions & 1 deletion src/adapters/test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import adapters from "./";
import { importBridgeNetwork } from "../data/importBridgeNetwork";
import { getLlamaPrices } from "../utils/prices";
import { transformTokens } from "../helpers/tokenMappings";
import { isAsyncAdapter } from "../utils/adapter";

const logTypes = {
txHash: "string",
Expand All @@ -24,7 +25,8 @@ const adapterName = process.argv[2];
const numberOfBlocks = process.argv[3];

const testAdapter = async () => {
const adapter = adapters[adapterName];
let adapter = adapters[adapterName];
adapter = isAsyncAdapter(adapter) ? await adapter.build() : adapter;
if (!adapter) {
throw new Error(`Adapter for ${adapterName} not found, check it is exported correctly.`);
}
Expand Down
80 changes: 80 additions & 0 deletions src/data/bridgeNetworkData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1608,4 +1608,84 @@ export default [
bitlayer: "btr",
},
},
{
id: 72,
displayName: "Hyperlane",
bridgeDbName: "hyperlane",
iconLink: "icons:hyperlane",
largeTxThreshold: 10000,
url: "https://hyperlane.xyz/",
chains: [
"Aleph Zero EVM",
"Ancient8",
"Arbitrum",
"Arthera",
"Astar",
"Astar zkEVM",
"Avalanche",
"Base",
"Bitlayer",
"Blast",
"BOB",
"BSC",
"Celo",
// "CheeseChain",
"Chiliz",
"Core",
"Cyber",
"Degen",
"Dogechain",
"Endurance",
"Ethereum",
"Everclear",
"Flare",
"Forma",
"Fraxtal",
"Fuse",
"Gnosis",
"Immutable zkEVM",
"Injective EVM",
"KalyChain",
"Kroma",
"Linea",
"Lisk",
"LUKSO",
// "Lumia Prism",
"Manta",
"Mantle",
"Merlin",
"Metis",
"Mint",
"Mode",
"Molten",
"Moonbeam",
"Oort",
"Optimism",
"Polygon",
"Polygon zkEVM",
"Proof of Play Apex",
"PulseChain",
"RARI Chain",
"re.al",
"Redstone",
"Rootstock",
"Sanko",
"Scroll",
"Sei",
"Shibarium",
"Superposition",
"Taiko",
"Tangle",
"Viction",
"World Chain",
"Xai",
"X Layer",
"ZetaChain",
"Zircuit",
"Zora",
],
chainMapping: {
avalanche: "avax",
},
},
] as BridgeNetwork[];
8 changes: 8 additions & 0 deletions src/helpers/bridgeAdapter.type.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { Chain } from "@defillama/sdk/build/general";
import { LlamaProvider } from "@defillama/sdk/build/util/LlamaProvider";
import { ethers } from "ethers";
import { EventKeyMapping } from "../utils/types";
import { EventData } from "../utils/types";

export type BridgeAdapter = {
[chain: string]: (fromBlock: number, toBlock: number) => Promise<EventData[]>;
};

export type AsyncBridgeAdapter = {
isAsync: boolean;
build: () => Promise<BridgeAdapter>;
};

export type EventLogFilter = {
includeToken?: string[];
includeFrom?: string[];
Expand All @@ -16,6 +23,7 @@ export type EventLogFilter = {
includeArg?: { [key: string]: string }[];
excludeArg?: { [key: string]: string }[];
includeTxData?: { [key: string]: string }[];
custom?: (provider: LlamaProvider, iface: ethers.utils.Interface, transactionHash: string) => Promise<boolean>;
};

export type FunctionSignatureFilter = {
Expand Down
4 changes: 4 additions & 0 deletions src/helpers/processTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ export const getTxDataFromEVMEventLogs = async (
if (toFilter) dataKeysToFilter.push(i);
}
}
if (filter?.custom) {
let toFilter = await filter.custom(provider, iface, txLog.transactionHash);
if (toFilter) dataKeysToFilter.push(i);
}
if (getTokenFromReceipt && getTokenFromReceipt.token) {
const txReceipt = await provider.getTransactionReceipt(txLog.transactionHash);
if (!txReceipt) {
Expand Down
Loading

0 comments on commit d14de44

Please sign in to comment.