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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"node": ">=22.18.0"
},
"dependencies": {
"@across-protocol/constants": "^3.1.102",
"@across-protocol/contracts": "5.0.4",
"@across-protocol/sdk": "4.3.136",
"@across-protocol/constants": "^3.1.105",
"@across-protocol/contracts": "5.0.5-alpha.0",
"@across-protocol/sdk": "4.3.137-alpha.3",
"@arbitrum/sdk": "^4.0.2",
"@consensys/linea-sdk": "^0.3.0",
"@coral-xyz/anchor": "^0.31.1",
Expand Down
3 changes: 2 additions & 1 deletion src/adapter/BaseChainAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
stringifyThrownValue,
ZERO_BYTES,
isEVMSpokePoolClient,
isTVMSpokePoolClient,
EvmAddress,
chainIsEvm,
sendAndConfirmSolanaTransaction,
Expand Down Expand Up @@ -111,7 +112,7 @@ export class BaseChainAdapter {
protected getSigner(chainId: number): Signer {
const spokePoolClient = this.spokePoolManager.getClient(chainId);
assert(isDefined(spokePoolClient), `SpokePoolClient not found for chainId ${chainId}`);
assert(isEVMSpokePoolClient(spokePoolClient));
assert(isEVMSpokePoolClient(spokePoolClient) || isTVMSpokePoolClient(spokePoolClient));
return spokePoolClient.spokePool.signer;
}

Expand Down
4 changes: 2 additions & 2 deletions src/adapter/bridges/OFTBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ export class OFTBridge extends BaseBridgeAdapter {
);

// Route discovery via configured IOFT messengers: if both L1 and L2 messengers exist, the route exists
const l1OftMessenger = OFT.getMessengerEvm(l1TokenAddress, l1ChainId);
const l2OftMessenger = OFT.getMessengerEvm(l1TokenAddress, l2ChainId);
const l1OftMessenger = OFT.getMessengerEvm(l1TokenAddress, l1ChainId, l2ChainId);
const l2OftMessenger = OFT.getMessengerEvm(l1TokenAddress, l2ChainId, l2ChainId);

super(l2ChainId, l1ChainId, l1Signer, [l1OftMessenger]);

Expand Down
4 changes: 2 additions & 2 deletions src/adapter/l2Bridges/OFTL2Bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ export class OFTL2Bridge extends BaseL2BridgeAdapter {
assert(translatedL2Token.isEVM());
this.l2Token = translatedL2Token;

const l1OftMessenger = OFT.getMessengerEvm(l1Token, hubChainId);
const l2OftMessenger = OFT.getMessengerEvm(l1Token, l2chainId);
const l1OftMessenger = OFT.getMessengerEvm(l1Token, hubChainId, l2chainId);
const l2OftMessenger = OFT.getMessengerEvm(l1Token, l2chainId, l2chainId);

this.nativeFeeCap = OFT_FEE_CAP_OVERRIDES[this.l2chainId] ?? OFT_DEFAULT_FEE_CAP;

Expand Down
5 changes: 4 additions & 1 deletion src/clients/ProfitClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -873,12 +873,15 @@ export class ProfitClient {
// Fallback to Coingecko's free API for now.
// TODO: Add support for Coingecko Pro.
const coingeckoProApiKey = undefined;
const spokePoolAddress = chainIsEvm(chainId)
? toAddressType(getDeployedAddress("SpokePool", chainId), chainId).toEvmAddress()
: getDeployedAddress("SvmSpoke", chainId);
// Call the factory to create a new QueryBase instance.
return relayFeeCalculator.QueryBase__factory.create(
chainId,
provider,
undefined, // symbolMapping
chainIsEvm(chainId) ? undefined : getDeployedAddress("SvmSpoke", chainId), // spokePoolAddress
spokePoolAddress,
undefined, // simulatedRelayerAddress
coingeckoProApiKey,
this.logger
Expand Down
9 changes: 5 additions & 4 deletions src/clients/TokenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
getRemoteTokenForL1Token,
getTokenInfo,
isEVMSpokePoolClient,
isTVMSpokePoolClient,
assert,
Address,
toAddressType,
Expand Down Expand Up @@ -240,7 +241,7 @@ export class TokenClient {
resolveRemoteTokens(chainId: number, hubPoolTokens: L1Token[]): Contract[] {
const spokePoolClient = this.spokePoolManager.getClient(chainId);
assert(isDefined(spokePoolClient), `SpokePoolClient not found for chainId ${chainId}`);
assert(isEVMSpokePoolClient(spokePoolClient));
assert(isEVMSpokePoolClient(spokePoolClient) || isTVMSpokePoolClient(spokePoolClient));
const { provider } = spokePoolClient.spokePool;
const erc20 = this.erc20.connect(provider);

Expand Down Expand Up @@ -292,7 +293,7 @@ export class TokenClient {
const spokePoolClient = this.spokePoolManager.getClient(chainId);
assert(isDefined(spokePoolClient), `SpokePoolClient not found for chainId ${chainId}`);

if (isEVMSpokePoolClient(spokePoolClient)) {
if (isEVMSpokePoolClient(spokePoolClient) || isTVMSpokePoolClient(spokePoolClient)) {
return this.updateEVM(chainId, hubPoolTokens);
} else if (isSVMSpokePoolClient(spokePoolClient)) {
return this.fetchSolanaTokenData(chainId, hubPoolTokens);
Expand Down Expand Up @@ -341,7 +342,7 @@ export class TokenClient {
): Promise<Record<string, { balance: BigNumber; allowance: BigNumber }>> {
const spokePoolClient = this.spokePoolManager.getClient(chainId);
assert(isDefined(spokePoolClient), `SpokePoolClient not found for chainId ${chainId}`);
assert(isEVMSpokePoolClient(spokePoolClient));
assert(isEVMSpokePoolClient(spokePoolClient) || isTVMSpokePoolClient(spokePoolClient));

const tokenData = Object.fromEntries(
await sdkUtils.mapAsync(this.resolveRemoteTokens(chainId, hubPoolTokens), async (token: Contract) => {
Expand All @@ -356,7 +357,7 @@ export class TokenClient {

private async updateEVM(chainId: number, hubPoolTokens: L1Token[]) {
const spokePoolClient = this.spokePoolManager.getClient(chainId);
assert(isEVMSpokePoolClient(spokePoolClient));
assert(isEVMSpokePoolClient(spokePoolClient) || isTVMSpokePoolClient(spokePoolClient));
const {
spokePool: { provider },
spokePoolAddress,
Expand Down
7 changes: 4 additions & 3 deletions src/clients/bridges/AdapterManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
getRemoteTokenForL1Token,
getTokenInfo,
isEVMSpokePoolClient,
isTVMSpokePoolClient,
Address,
isSVMSpokePoolClient,
bnZero,
Expand Down Expand Up @@ -84,7 +85,7 @@ export class AdapterManager {
SUPPORTED_TOKENS[chainId]?.map((symbol) => {
const spokePoolClient = this.spokePoolManager.getClient(chainId);
let l2SignerOrProvider;
if (isEVMSpokePoolClient(spokePoolClient)) {
if (isEVMSpokePoolClient(spokePoolClient) || isTVMSpokePoolClient(spokePoolClient)) {
l2SignerOrProvider = spokePoolClient.spokePool.signer;
} else if (isSVMSpokePoolClient(spokePoolClient)) {
l2SignerOrProvider = spokePoolClient.svmEventsClient.getRpc();
Expand All @@ -109,7 +110,7 @@ export class AdapterManager {
}
const spokePoolClient = this.spokePoolManager.getClient(chainId);
let l2SignerOrSvmProvider;
if (isEVMSpokePoolClient(spokePoolClient)) {
if (isEVMSpokePoolClient(spokePoolClient) || isTVMSpokePoolClient(spokePoolClient)) {
l2SignerOrSvmProvider = spokePoolClient.spokePool.signer;
} else if (isSVMSpokePoolClient(spokePoolClient)) {
l2SignerOrSvmProvider = spokePoolClient.svmEventsClient.getRpc();
Expand Down Expand Up @@ -325,7 +326,7 @@ export class AdapterManager {
getSigner(chainId: number): Signer {
const spokePoolClient = this.spokePoolManager.getClient(chainId);
assert(isDefined(spokePoolClient), `SpokePoolClient not found for chainId ${chainId}`);
assert(isEVMSpokePoolClient(spokePoolClient));
assert(isEVMSpokePoolClient(spokePoolClient) || isTVMSpokePoolClient(spokePoolClient));
return spokePoolClient.spokePool.signer;
}

Expand Down
4 changes: 3 additions & 1 deletion src/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ export type SpokePoolManager = clients.SpokePoolManager;
export type SpokePoolClient = clients.SpokePoolClient;
export type EVMSpokePoolClient = clients.EVMSpokePoolClient;
export type SVMSpokePoolClient = clients.SVMSpokePoolClient;
export type TVMSpokePoolClient = clients.TVMSpokePoolClient;
export type SpokePoolUpdate = clients.SpokePoolUpdate;
export const { EVMSpokePoolClient, SpokePoolClient, SVMSpokePoolClient, SpokePoolManager } = clients;
export const { EVMSpokePoolClient, SpokePoolClient, SVMSpokePoolClient, SpokePoolManager, TVMSpokePoolClient } =
clients;

export { SpokeListener, SpokePoolClientWithListener, isSpokePoolClientWithListener } from "./SpokePoolClient";
export class BundleDataClient extends clients.BundleDataClient.BundleDataClient {}
Expand Down
18 changes: 15 additions & 3 deletions src/common/ClientHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import assert from "assert";
import winston from "winston";
import {
chainIsSvm,
chainIsTvm,
getProvider,
getDeployedContract,
getDeploymentBlockNumber,
Expand All @@ -17,6 +18,7 @@ import {
chainIsEvm,
forEachAsync,
isEVMSpokePoolClient,
isTVMSpokePoolClient,
getSvmProvider,
getBlockFinder,
} from "../utils";
Expand All @@ -26,6 +28,7 @@ import {
ConfigStoreClient,
EVMSpokePoolClient,
SVMSpokePoolClient,
TVMSpokePoolClient,
SpokePoolClient,
} from "../clients";
import { CommonConfig } from "./Config";
Expand Down Expand Up @@ -309,10 +312,19 @@ export async function getSpokePoolClientsForContract(
}
const spokePoolClientSearchSettings = {
from: fromBlocks[chainId] ? Math.max(fromBlocks[chainId], registrationBlock) : registrationBlock,
to: toBlocks[chainId],
to: chainIsTvm(chainId) ? undefined : toBlocks[chainId],
maxLookBack: config.maxBlockLookBack[chainId],
};
if (chainIsEvm(chainId)) {
if (chainIsTvm(chainId)) {
spokePoolClients[chainId] = new TVMSpokePoolClient(
logger,
contract,
hubPoolClient,
chainId,
registrationBlock,
spokePoolClientSearchSettings
);
} else if (chainIsEvm(chainId)) {
spokePoolClients[chainId] = new EVMSpokePoolClient(
logger,
contract,
Expand Down Expand Up @@ -423,7 +435,7 @@ export function spokePoolClientsToProviders(spokePoolClients: { [chainId: number
return Object.fromEntries(
Object.entries(spokePoolClients)
.map(([chainId, client]): [number, ethers.providers.Provider] => {
if (isEVMSpokePoolClient(client)) {
if (isEVMSpokePoolClient(client) || isTVMSpokePoolClient(client)) {
return [Number(chainId), client.spokePool.signer.provider];
}
return [Number(chainId), undefined];
Expand Down
33 changes: 32 additions & 1 deletion src/common/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,14 @@ export const MAX_RELAYER_DEPOSIT_LOOK_BACK = 4 * 60 * 60;
export const FINALIZER_TOKENBRIDGE_LOOKBACK = 14 * 24 * 60 * 60;

// Chain IDs using the Succinct/Helios SP1 messaging bridge.
export const UNIVERSAL_CHAINS = [CHAIN_IDs.BSC, CHAIN_IDs.HYPEREVM, CHAIN_IDs.PLASMA, CHAIN_IDs.MONAD, CHAIN_IDs.TEMPO];
export const UNIVERSAL_CHAINS = [
CHAIN_IDs.BSC,
CHAIN_IDs.HYPEREVM,
CHAIN_IDs.PLASMA,
CHAIN_IDs.MONAD,
CHAIN_IDs.TEMPO,
CHAIN_IDs.TRON,
];

// Reorgs are anticipated on Ethereum and Polygon. We use different following distances when processing deposit
// events based on the USD amount of the deposit. This protects the relayer from the worst case situation where it fills
Expand Down Expand Up @@ -118,6 +125,7 @@ export const MIN_DEPOSIT_CONFIRMATIONS: { [threshold: number | string]: { [chain
[CHAIN_IDs.POLYGON]: 64, // Probabilistically safe level based on historic Polygon reorgs
[CHAIN_IDs.BSC]: 2, // Average takes 2.5 blocks to finalize but reorgs rarely happen
[CHAIN_IDs.SCROLL]: 8,
[CHAIN_IDs.TRON]: 19, // 19 Blocks to finalize.
},
100: {
[CHAIN_IDs.HYPEREVM]: 1,
Expand All @@ -129,6 +137,7 @@ export const MIN_DEPOSIT_CONFIRMATIONS: { [threshold: number | string]: { [chain
[CHAIN_IDs.POLYGON]: 16,
[CHAIN_IDs.SCROLL]: 2,
[CHAIN_IDs.TEMPO]: 2,
[CHAIN_IDs.TRON]: 0,
[CHAIN_IDs.BSC]: 0,
[CHAIN_IDs.ZK_SYNC]: 0,
},
Expand Down Expand Up @@ -191,6 +200,7 @@ const resolveChainBundleBuffers = () => {
[CHAIN_IDs.POLYGON]: 128, // ~2s/block. Polygon has historically re-orged often.
[CHAIN_IDs.SCROLL]: 40, // ~3s/block.
[CHAIN_IDs.TEMPO]: 400, // ~500ms a block.
[CHAIN_IDs.TRON]: 40, // 3s/block.
[CHAIN_IDs.ZK_SYNC]: defaultBuffers[ChainFamily.ZK_STACK], // Inherit ZK_STACK default.
};

Expand Down Expand Up @@ -244,6 +254,7 @@ const resolveChainCacheDelay = () => {
[CHAIN_IDs.POLYGON]: 256,
[CHAIN_IDs.SCROLL]: 100,
[CHAIN_IDs.TEMPO]: 400,
[CHAIN_IDs.TRON]: 20, // 19 blocks for finalization.
[CHAIN_IDs.ZK_SYNC]: cacheDelays[ChainFamily.ZK_STACK],
};

Expand Down Expand Up @@ -281,6 +292,7 @@ export const DEFAULT_NO_TTL_DISTANCE: { [chainId: number]: number } = {
[CHAIN_IDs.SOLANA]: 432000,
[CHAIN_IDs.SONEIUM]: 86400,
[CHAIN_IDs.TEMPO]: 345600,
[CHAIN_IDs.TRON]: 57600,
[CHAIN_IDs.UNICHAIN]: 86400,
[CHAIN_IDs.WORLD_CHAIN]: 86400,
[CHAIN_IDs.ZK_SYNC]: 172800,
Expand Down Expand Up @@ -351,6 +363,7 @@ export const SUPPORTED_TOKENS: { [chainId: number]: string[] } = {
[CHAIN_IDs.SOLANA]: ["USDC"],
[CHAIN_IDs.SONEIUM]: ["WETH", "USDC"],
[CHAIN_IDs.TEMPO]: ["USDC"],
[CHAIN_IDs.TRON]: ["USDT"],
[CHAIN_IDs.UNICHAIN]: ["ETH", "WETH", "USDC", "USDT", "ezETH"],
[CHAIN_IDs.WORLD_CHAIN]: ["WETH", "WBTC", "USDC", "WLD", "POOL"],
[CHAIN_IDs.ZK_SYNC]: ["USDC", "USDT", "WETH", "WBTC", "DAI"],
Expand Down Expand Up @@ -528,6 +541,9 @@ export const CUSTOM_BRIDGE: Record<number, Record<string, L1BridgeConstructor<Ba
[CHAIN_IDs.TEMPO]: {
[TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.MAINNET]]: TokenSplitterBridge,
},
[CHAIN_IDs.TRON]: {
[TOKEN_SYMBOLS_MAP.USDT.addresses[CHAIN_IDs.MAINNET]]: OFTBridge,
},
[CHAIN_IDs.UNICHAIN]: {
[TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.MAINNET]]: UsdcCCTPBridge,
[TOKEN_SYMBOLS_MAP.USDT.addresses[CHAIN_IDs.MAINNET]]: OFTBridge,
Expand Down Expand Up @@ -659,6 +675,9 @@ export const CUSTOM_L2_BRIDGE: Record<number, Record<string, L2BridgeConstructor
[CHAIN_IDs.TEMPO]: {
[TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.MAINNET]]: OFTL2Bridge,
},
[CHAIN_IDs.TRON]: {
[TOKEN_SYMBOLS_MAP.USDT.addresses[CHAIN_IDs.MAINNET]]: OFTL2Bridge,
},
[CHAIN_IDs.UNICHAIN]: {
[TOKEN_SYMBOLS_MAP.ezETH.addresses[CHAIN_IDs.MAINNET]]: HyperlaneXERC20BridgeL2,
[TOKEN_SYMBOLS_MAP.USDC.addresses[CHAIN_IDs.MAINNET]]: L2UsdcCCTPBridge,
Expand Down Expand Up @@ -965,6 +984,18 @@ export const EVM_OFT_MESSENGERS: Map<string, Map<number, EvmAddress>> = new Map(
],
]);

export const LEGACY_MESH_NETWORKS: number[] = [CHAIN_IDs.TRON];

export const EVM_LEGACY_MESH_MESSENGERS: Map<string, Map<number, EvmAddress>> = new Map([
[
TOKEN_SYMBOLS_MAP.USDT.addresses[CHAIN_IDs.MAINNET],
new Map<number, EvmAddress>([
[CHAIN_IDs.MAINNET, EvmAddress.from("0x1F748c76dE468e9D11bd340fA9D5CBADf315dFB0")],
[CHAIN_IDs.TRON, EvmAddress.from("0x3a08f76772e200653bb55c2a92998daca62e0e97")], // Cast this address to an EvmAddress
]),
],
]);

// In rare circumstances, we may need to pay the LZ bridge fee in a custom token, such as when
// the network we are bridging on does not have a native token. Specify that here.
export const LZ_FEE_TOKENS: { [chainId: number]: EvmAddress } = {
Expand Down
9 changes: 5 additions & 4 deletions src/rebalancer/adapters/oftAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export class OftAdapter extends BaseAdapter {
const connectedSigner = this.baseSigner.connect(await getProvider(chainId));
if (EVM_OFT_MESSENGERS.get(TOKEN_SYMBOLS_MAP.USDT.addresses[CHAIN_IDs.MAINNET])?.has(chainId)) {
const usdt = new Contract(this._getTokenInfo("USDT", chainId).address.toNative(), ERC20.abi, connectedSigner);
const oftMessenger = await this._getOftMessenger(chainId);
const oftMessenger = await this._getOftMessenger(chainId, chainId);
const oftAllowance = await usdt.allowance(this.baseSignerAddress.toNative(), oftMessenger.address);
if (oftAllowance.lt(toBN(MAX_SAFE_ALLOWANCE).div(2))) {
this.multicallerClient.enqueueTransaction({
Expand Down Expand Up @@ -187,10 +187,11 @@ export class OftAdapter extends BaseAdapter {
return this._redisGetPendingBridgesPreDeposit();
}

protected async _getOftMessenger(chainId: number): Promise<Contract> {
protected async _getOftMessenger(chainId: number, destinationChainId: number): Promise<Contract> {
const oftMessengerAddress = getMessengerEvm(
EvmAddress.from(this._getTokenInfo("USDT", CHAIN_IDs.MAINNET).address.toNative()),
chainId
chainId,
destinationChainId
);
const originProvider = await getProvider(chainId);
return new Contract(oftMessengerAddress.toNative(), IOFT_ABI_FULL, this.baseSigner.connect(originProvider));
Expand All @@ -205,7 +206,7 @@ export class OftAdapter extends BaseAdapter {
sendParamStruct: SendParamStruct;
oftMessenger: Contract;
}> {
const oftMessenger = await this._getOftMessenger(originChain);
const oftMessenger = await this._getOftMessenger(originChain, destinationChain);
const sharedDecimals = await oftMessenger.sharedDecimals();

const roundedAmount = roundAmountToSend(
Expand Down
4 changes: 4 additions & 0 deletions src/utils/BridgeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@ import axios, { RawAxiosRequestHeaders } from "axios";
// API supports multiple destination tokens for a single L1 token.
export const BRIDGE_API_DESTINATION_TOKENS: { [l2ChainId: number]: string } = {
[CHAIN_IDs.TEMPO]: TOKEN_SYMBOLS_MAP.pathUSD.addresses[CHAIN_IDs.TEMPO],
[CHAIN_IDs.TRON]: TOKEN_SYMBOLS_MAP.USDT.addresses[CHAIN_IDs.TRON],
};

export const BRIDGE_API_DESTINATION_TOKEN_SYMBOLS: { [address: string]: string } = {
[TOKEN_SYMBOLS_MAP.pathUSD.addresses[CHAIN_IDs.TEMPO]]: "path_usd",
[TOKEN_SYMBOLS_MAP.USDT.addresses[CHAIN_IDs.TRON]]: "usdt",
};

const NETWORK_NAMES: { [chainId: number]: string } = {
[CHAIN_IDs.MAINNET]: "ethereum",
[CHAIN_IDs.TEMPO]: "tempo",
[CHAIN_IDs.TRON]: "tron",
};

export const BRIDGE_API_MINIMUMS: { [sourceChainId: number]: { [dstChainId: number]: BigNumber } } = {
[CHAIN_IDs.MAINNET]: {
[CHAIN_IDs.TEMPO]: toBN(5_000_000), // 5 USD
[CHAIN_IDs.TRON]: toBN(5_000_000), // 5 USD
},
[CHAIN_IDs.TEMPO]: {
[CHAIN_IDs.MAINNET]: toBN(5_000_000), // 5 USD
Expand Down
4 changes: 2 additions & 2 deletions src/utils/ContractUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import {
getDeployedBlockNumber,
EvmAddress,
chainIsEvm,
SvmAddress,
Address,
toAddressType,
isDefined,
} from ".";
import { CONTRACT_ADDRESSES } from "../common";
Expand Down Expand Up @@ -58,7 +58,7 @@ export function getPermit2(chainId: number, address?: string): Contract {
export function getSpokePoolAddress(chainId: number): Address {
const evmChain = chainIsEvm(chainId);
const addr = getDeployedAddress(evmChain ? "SpokePool" : "SvmSpoke", chainId, true);
return evmChain ? EvmAddress.from(addr) : SvmAddress.from(addr);
return toAddressType(addr, chainId);
}

export function getHubPoolAddress(chainId: number): EvmAddress {
Expand Down
Loading
Loading