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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 38 additions & 16 deletions src/adapter/BaseChainAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import {
sendAndConfirmSolanaTransaction,
getSvmProvider,
submitTransaction,
getTokenInfo,
ConvertDecimals,
toAddressType,
} from "../utils";
import { AugmentedTransaction, TransactionClient } from "../clients/TransactionClient";
import {
Expand Down Expand Up @@ -69,7 +72,7 @@ export class BaseChainAdapter {
spokePoolClients: { [chainId: number]: SpokePoolClient },
protected readonly chainId: number,
protected readonly hubChainId: number,
protected readonly monitoredAddresses: Address[],
protected readonly monitoredAddresses: { [l1Token: string]: Address[] },
protected readonly logger: winston.Logger,
public readonly supportedTokens: SupportedTokenSymbol[],
protected readonly bridges: { [l1Token: string]: BaseBridgeAdapter },
Expand Down Expand Up @@ -532,8 +535,17 @@ export class BaseChainAdapter {

const outstandingTransfers: OutstandingTransfers = {};

await forEachAsync(this.monitoredAddresses, async (monitoredAddress) => {
await forEachAsync(availableL1Tokens, async (l1Token) => {
this.logger.debug({
at: `${this.adapterName}#getOutstandingCrossChainTransfers`,
message: "Getting outstanding cross chain transfers",
monitoredAddresses: Object.fromEntries(
Object.entries(this.monitoredAddresses).map(([l1Token, addresses]) => [l1Token, addresses])
),
});

await forEachAsync(availableL1Tokens, async (l1Token) => {
const monitoredAddresses = this.monitoredAddresses[l1Token.toNative()];
await forEachAsync(monitoredAddresses, async (monitoredAddress) => {
const bridge = this.bridges[l1Token.toNative()];
const [depositInitiatedResults, depositFinalizedResults] = await Promise.all([
bridge.queryL1BridgeInitiationEvents(l1Token, monitoredAddress, monitoredAddress, l1SearchConfig),
Expand All @@ -544,27 +556,37 @@ export class BaseChainAdapter {
this.chainId,
monitoredAddress
);

Object.entries(depositInitiatedResults).forEach(([l2Token, depositInitiatedEvents]) => {
const trackedInitiatedEvents = depositInitiatedEvents.filter(
const filteredDepositEvents = (depositInitiatedEvents ?? []).filter(
(event) => !ignoredPendingBridgeTxnRefs.has(event.txnRef)
);
const finalizedAmounts = depositFinalizedResults?.[l2Token]?.map((event) => event.amount.toString()) ?? [];
const outstandingInitiatedEvents: typeof trackedInitiatedEvents = [];
const totalAmount = trackedInitiatedEvents.reduce((acc, event) => {
// Remove the first match. This handles scenarios where are collisions by amount.
const index = finalizedAmounts.indexOf(event.amount.toString());
if (index > -1) {
finalizedAmounts.splice(index, 1);
return acc;
}
outstandingInitiatedEvents.push(event);
const totalDepositedAmount = filteredDepositEvents.reduce((acc, event) => {
return acc.add(event.amount);
}, bnZero);
const l2TokenDecimals = getTokenInfo(toAddressType(l2Token, this.chainId), this.chainId).decimals;
const l1TokenDecimals = getTokenInfo(l1Token, this.hubChainId).decimals;
const totalFinalizedAmount = (depositFinalizedResults?.[l2Token] ?? []).reduce((acc, event) => {
return acc.add(ConvertDecimals(l2TokenDecimals, l1TokenDecimals)(event.amount));
}, bnZero);

// If there is a net unfinalized amount, go through deposit initiated events in newest to oldest order
// and assume that the newest bridges are the ones that are not yet finalized.
const outstandingInitiatedEvents: string[] = [];
const totalAmount = totalDepositedAmount.sub(totalFinalizedAmount);
let remainingUnfinalizedAmount = totalAmount;
if (remainingUnfinalizedAmount.gt(0)) {
for (const depositEvent of filteredDepositEvents) {
if (remainingUnfinalizedAmount.lte(0)) {
break;
}
outstandingInitiatedEvents.push(depositEvent.txnRef);
remainingUnfinalizedAmount = remainingUnfinalizedAmount.sub(depositEvent.amount);
}
}
if (totalAmount.gt(0) && outstandingInitiatedEvents.length > 0) {
assign(outstandingTransfers, [monitoredAddress.toNative(), l1Token.toNative(), l2Token], {
totalAmount,
depositTxHashes: outstandingInitiatedEvents.map((event) => event.txnRef),
depositTxHashes: outstandingInitiatedEvents,
});
}
});
Expand Down
8 changes: 5 additions & 3 deletions src/adapter/bridges/BinanceCEXBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
getBinanceDepositType,
BinanceTransactionType,
getBinanceWithdrawalType,
toAddressType,
} from "../../utils";
import { BaseBridgeAdapter, BridgeTransactionDetails, BridgeEvents, BridgeEvent } from "./BaseBridgeAdapter";
import ERC20_ABI from "../../common/abi/MinimalERC20.json";
Expand Down Expand Up @@ -159,12 +160,13 @@ export class BinanceCEXBridge extends BaseBridgeAdapter {
withdrawalType !== BinanceTransactionType.SWAP
);
});
const { decimals: l1Decimals } = getTokenInfo(l1Token, this.hubChainId);
const l2TokenAddress = this.resolveL2TokenAddress(l1Token);
const { decimals: l2Decimals } = getTokenInfo(toAddressType(l2TokenAddress, this.l2chainId), this.l2chainId);

return {
[this.resolveL2TokenAddress(l1Token)]: await mapAsync(withdrawalHistory, async (withdrawal) => {
[l2TokenAddress]: await mapAsync(withdrawalHistory, async (withdrawal) => {
const txnReceipt = await this.l2Provider.getTransactionReceipt(withdrawal.txId);
return this.toBridgeEvent(floatToBN(withdrawal.amount, l1Decimals), txnReceipt);
return this.toBridgeEvent(floatToBN(withdrawal.amount, l2Decimals), txnReceipt);
}),
};
}
Expand Down
2 changes: 1 addition & 1 deletion src/adapter/bridges/BridgeApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ export class BridgeApi extends BaseBridgeAdapter {
const statusesGrouped = groupObjectCountsByProp(pendingTransfers, (pendingTransfer) => pendingTransfer.state);
this.logger.debug({
at: "BridgeApi#queryL1BridgeInitiationEvents",
message: "Pending transfer statuses",
message: `Pending transfer statuses for ${this.l1TokenInfo.symbol} and ${toAddress}`,
statusesGrouped,
});

Expand Down
103 changes: 0 additions & 103 deletions src/clients/TokenTransferClient.ts

This file was deleted.

34 changes: 31 additions & 3 deletions src/clients/bridges/AdapterManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,19 @@ export class AdapterManager {
(client) => client.spokePoolAddress
);

const isHubPoolOrSpokePoolAddress = (chainId: number, address: Address) => {
return (
EvmAddress.from(this.hubPoolClient.hubPool.address).eq(address) ||
this.spokePoolManager.getClient(chainId)?.spokePoolAddress.eq(address)
);
};

// The adapters are only set up to monitor EOA's and the HubPool and SpokePool address, so remove
// spoke pool addresses from other chains.
const filterMonitoredAddresses = (chainId: number) => {
return monitoredAddresses.filter(
(address) =>
EvmAddress.from(this.hubPoolClient.hubPool.address).eq(address) ||
this.spokePoolManager.getClient(chainId)?.spokePoolAddress.eq(address) ||
isHubPoolOrSpokePoolAddress(chainId, address) ||
!spokePoolAddresses.some((spokePoolAddress) => spokePoolAddress.eq(address))
);
};
Expand Down Expand Up @@ -129,12 +135,34 @@ export class AdapterManager {
);
};
Object.values(this.spokePoolManager.getSpokePoolClients()).map(({ chainId }) => {
// Filter hub/spoke pool addresses from the monitored addresses for chains that don't have a pool rebalance
// route for the l1 token.
const monitoredAddresses = Object.fromEntries(
(SUPPORTED_TOKENS[chainId] ?? []).map((symbol) => {
const l1Token = TOKEN_SYMBOLS_MAP[symbol].addresses[hubChainId];
return [
l1Token,
filterMonitoredAddresses(chainId).filter((address) => {
if (!l1Token) {
return false;
}
const hasPoolRebalanceRoute = hubPoolClient.l2TokenEnabledForL1Token(EvmAddress.from(l1Token), chainId);
if (!hasPoolRebalanceRoute) {
// Chain does not have a pool rebalance route, only allow EOA's.
return !isHubPoolOrSpokePoolAddress(chainId, address);
}
// Chain has pool rebalance route, all addresses from filterMonitoredAddresses are valid.
return true;
}),
];
})
);
// Instantiate a generic adapter and supply all network-specific configurations.
this.adapters[chainId] = new BaseChainAdapter(
this.spokePoolManager.getSpokePoolClients(),
chainId,
hubChainId,
filterMonitoredAddresses(chainId),
monitoredAddresses,
logger,
SUPPORTED_TOKENS[chainId] ?? [],
constructBridges(chainId),
Expand Down
2 changes: 0 additions & 2 deletions src/clients/bridges/CrossChainTransferClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ export class CrossChainTransferClient {
return;
}

this.log("Updating cross chain transfers", { chainIds });

const outstandingTransfersPerChain = await Promise.all(
chainIds.map(async (chainId) => [
chainId,
Expand Down
1 change: 0 additions & 1 deletion src/clients/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ export * from "./ConfigStoreClient";
export * from "./MultiCallerClient";
export * from "./ProfitClient";
export * from "./TokenClient";
export * from "./TokenTransferClient";
export * from "./TransactionClient";
export * from "./InventoryClient";
export * from "./AcrossAPIClient";
Expand Down
39 changes: 0 additions & 39 deletions src/interfaces/Report.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,5 @@
import { BigNumber } from "../utils";

export enum BundleAction {
PROPOSED = "proposed",
DISPUTED = "disputed",
CANCELED = "canceled",
}

export enum BalanceType {
// Current balance.
CURRENT = "current",
// Balance from any pending bundle's refunds.
PENDING = "pending",
// Balance from next bundle's refunds.
NEXT = "next",
// Balance from pending cross chain transfers.
PENDING_TRANSFERS = "pending transfers",
// Total balance across current, pending, next.
TOTAL = "total",
}

export interface RelayerBalanceCell {
// This should match BalanceType values. We can't use BalanceType directly because interface only accepts static keys,
// not those, such as enums, that are determined at runtime.
[balanceType: string]: BigNumber;
}

export interface RelayerBalanceColumns {
// Including a column for "Total".
[chainName: string]: RelayerBalanceCell;
}

// The relayer balance table is organized as below:
// 1. Rows are token symbols, e.g. WETH, USDC
// 2. Columns are chains, e.g. Optimism.
// 3. Each column is further broken into subcolumns: current (live balance), pending (balance from pending bundle's refunds)
// and next bundle (balance from next bundle's refunds)
export interface RelayerBalanceTable {
[tokenSymbol: string]: RelayerBalanceColumns;
}

export interface RelayerBalanceReport {
[relayer: string]: RelayerBalanceTable;
}
19 changes: 0 additions & 19 deletions src/interfaces/Token.ts
Original file line number Diff line number Diff line change
@@ -1,19 +0,0 @@
import { BigNumber } from "../utils";
import { SortableEvent } from ".";

export interface TokenTransfer extends SortableEvent {
value: BigNumber;
from: string;
to: string;
}

export interface TransfersByTokens {
[token: string]: {
incoming: TokenTransfer[];
outgoing: TokenTransfer[];
};
}

export interface TransfersByChain {
[chainId: number]: TransfersByTokens;
}
1 change: 0 additions & 1 deletion src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { BigNumber } from "../utils";

export * from "./InventoryManagement";
export * from "./SpokePool";
export * from "./Token";
export * from "./Error";
export * from "./Report";
export * from "./Arweave";
Expand Down
Loading
Loading