Skip to content

Commit

Permalink
skip WETH rebalancing
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Mar 3, 2025
1 parent 62927d4 commit 2b84dcf
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 31 deletions.
2 changes: 1 addition & 1 deletion src/server/config/rebalance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const DEFAULT_REBALANCE_CONFIG: RebalanceConfig = {
},
// Unichain
130: {
targetPercentage: 50, // 50% of funds on Unichain
targetPercentage: 30, // 50% of funds on Unichain
triggerThreshold: 20, // Trigger rebalance if below 20%
sourcePriority: 3, // Third priority as source
canBeDestination: true, // Can be a destination for bridging
Expand Down
59 changes: 41 additions & 18 deletions src/server/services/across/AcrossService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,26 @@ export class AcrossService {
inputAmount: bigint,
destinationChainId: number,
recipient?: Address,
outputToken: Address = "0x0000000000000000000000000000000000000000"
outputToken?: Address
): AcrossDepositParams {
// Always use address(0) as the output token to receive ETH on the destination chain
// This is because Across automatically converts to ETH and doesn't support bridging to WETH directly
const finalOutputToken: Address =
"0x0000000000000000000000000000000000000000";

// Log if this is a WETH transfer
let isWethTransfer = false;
for (const [chainIdStr, config] of Object.entries(CHAIN_CONFIG)) {
if (
config.tokens.WETH.address.toLowerCase() === inputToken.toLowerCase()
) {
isWethTransfer = true;
this.logger.info(
`WETH transfer detected from chain ${chainIdStr} to chain ${destinationChainId}. Will be received as ETH.`
);
break;
}
}
// Calculate output amount by subtracting the relay fee from the input amount
const outputAmount = inputAmount - BigInt(feeResponse.totalRelayFee.total);

Expand All @@ -111,7 +129,7 @@ export class AcrossService {
depositor,
recipient: recipient || depositor,
inputToken,
outputToken,
outputToken: finalOutputToken,
inputAmount: inputAmount.toString(),
outputAmount: outputAmount.toString(),
destinationChainId,
Expand Down Expand Up @@ -166,12 +184,19 @@ export class AcrossService {
}
}

// Always set output token to address(0) to receive ETH on the destination chain
// This is because Across automatically converts to ETH and doesn't support bridging to WETH directly
adjustedDepositParams.outputToken =
"0x0000000000000000000000000000000000000000";

// Log the deposit parameters for debugging
this.logger.info("Executing deposit with parameters:", {
originChainId,
spokePoolAddress,
inputToken: adjustedDepositParams.inputToken,
outputToken: adjustedDepositParams.outputToken,
inputAmount: adjustedDepositParams.inputAmount,
outputAmount: adjustedDepositParams.outputAmount,
destinationChainId: adjustedDepositParams.destinationChainId,
recipient: adjustedDepositParams.recipient,
isEthTransfer,
Expand Down Expand Up @@ -412,22 +437,20 @@ export class AcrossService {
);
}

this.logger.info(
`${[
params.depositor,
params.recipient,
params.inputToken,
params.outputToken,
BigInt(params.inputAmount).toString(),
BigInt(params.outputAmount).toString(),
BigInt(params.destinationChainId).toString(),
params.exclusiveRelayer,
params.quoteTimestamp,
params.fillDeadline,
params.exclusivityDeadline,
params.message as `0x${string}`,
]}`
);
this.logger.info("Encoding deposit with parameters:", {
depositor: params.depositor,
recipient: params.recipient,
inputToken: params.inputToken,
outputToken: params.outputToken,
inputAmount: BigInt(params.inputAmount).toString(),
outputAmount: BigInt(params.outputAmount).toString(),
destinationChainId: BigInt(params.destinationChainId).toString(),
exclusiveRelayer: params.exclusiveRelayer,
quoteTimestamp: params.quoteTimestamp,
fillDeadline: params.fillDeadline,
exclusivityDeadline: params.exclusivityDeadline,
message: params.message,
});

// Encode the function call
const encodedCall = encodeFunctionData({
Expand Down
8 changes: 8 additions & 0 deletions src/server/services/across/BalanceAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,14 @@ export class BalanceAnalyzer {
specificToken
);

// Skip if the selected token is WETH - we never want to bridge WETH
if (tokenToRebalance === "WETH") {
this.logger.info(
`Skipping rebalance from chain ${sourceChain.chainId} to chain ${destinationChain.chainId} because WETH was selected, and we never want to bridge WETH`
);
continue; // Try the next source chain
}

// Log the selected token
this.logger.info(
`Selected token ${tokenToRebalance} for rebalance from chain ${sourceChain.chainId} to chain ${destinationChain.chainId}`,
Expand Down
68 changes: 58 additions & 10 deletions src/server/services/across/RebalanceDecisionMaker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export class RebalanceDecisionMaker {

/**
* Select the token to use for rebalancing
* Never selects WETH for rebalancing
*/
public selectTokenToRebalance(
sourceChain: {
Expand All @@ -58,17 +59,52 @@ export class RebalanceDecisionMaker {
let tokenToRebalance: string;
let tokenInfo: (typeof sourceChain.availableTokens)[0];

// Filter out WETH from available tokens - we never want to bridge WETH
const filteredAvailableTokens = sourceChain.availableTokens.filter(
(t) => t.token !== "WETH"
);

// Log available tokens for debugging
this.logger.debug(
"Available tokens for rebalance:",
sourceChain.availableTokens.map((t) => ({
"Available tokens for rebalance (excluding WETH):",
filteredAvailableTokens.map((t) => ({
token: t.token,
balanceUsd: t.balanceUsd,
priority: t.priority,
excessPercentage: t.excessPercentage || 0,
}))
);

// If specific token is WETH, use ETH instead
const effectiveSpecificToken =
specificToken === "WETH"
? (() => {
this.logger.info(
"WETH specified for rebalancing, using ETH instead"
);
return "ETH";
})()
: specificToken;

// If no tokens are available after filtering out WETH, return undefined
// The calling code will handle this case
if (filteredAvailableTokens.length === 0) {
this.logger.info(
"No tokens available for rebalancing after filtering out WETH"
);
// Use the first available token from the original list as a fallback
// This ensures we always return something, even if it's WETH
// The caller can decide whether to proceed with the rebalance
if (sourceChain.availableTokens.length > 0) {
tokenInfo = sourceChain.availableTokens[0];
tokenToRebalance = tokenInfo.token;
this.logger.debug(
`No non-WETH tokens available, falling back to: ${tokenToRebalance}`
);
return { tokenToRebalance, tokenInfo };
}
}

if (specificToken) {
// Find this token in the available tokens on the source chain
// Find the specific token in available tokens, prioritizing those with excess
Expand Down Expand Up @@ -145,7 +181,8 @@ export class RebalanceDecisionMaker {
}
} else {
// When no specific token is requested, prioritize tokens with excess
const tokensWithExcess = sourceChain.availableTokens.filter(
// Filter out WETH since we never want to bridge it
const tokensWithExcess = filteredAvailableTokens.filter(
(t) => t.excessPercentage && t.excessPercentage > 0
);

Expand All @@ -161,13 +198,24 @@ export class RebalanceDecisionMaker {
`No specific token requested, selected token with highest excess: ${tokenToRebalance} (${tokenInfo.excessPercentage?.toFixed(2)}%)`
);
} else {
// If no tokens with excess, fall back to highest balance
sourceChain.availableTokens.sort((a, b) => b.balanceUsd - a.balanceUsd);
tokenInfo = sourceChain.availableTokens[0];
tokenToRebalance = tokenInfo.token;
this.logger.debug(
`No tokens with excess, selected ${tokenToRebalance} by balance`
);
// If no tokens with excess, fall back to highest balance (excluding WETH)
if (filteredAvailableTokens.length > 0) {
filteredAvailableTokens.sort((a, b) => b.balanceUsd - a.balanceUsd);
tokenInfo = filteredAvailableTokens[0];
tokenToRebalance = tokenInfo.token;
this.logger.debug(
`No tokens with excess, selected ${tokenToRebalance} by balance (excluding WETH)`
);
} else {
// If no tokens are available after filtering out WETH, log a message
this.logger.info("No non-WETH tokens available for rebalancing");
// The amount calculation will skip this rebalance downstream
sourceChain.availableTokens.sort(
(a, b) => b.balanceUsd - a.balanceUsd
);
tokenInfo = sourceChain.availableTokens[0];
tokenToRebalance = tokenInfo.token;
}
}
}

Expand Down
11 changes: 9 additions & 2 deletions src/server/services/across/RebalanceService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,16 +160,23 @@ export class RebalanceService {
}

// Prepare deposit parameters using the AcrossService method
// Note: Across always converts WETH to ETH on the destination chain
const depositParams = this.acrossService.prepareDepositParams(
feeResponse,
this.accountAddress,
constantsTokenConfig.address,
BigInt(rawAmount),
toChainId,
undefined,
"0x0000000000000000000000000000000000000000" as Address
undefined // recipient (defaults to sender)
);

// Log if this is a WETH transfer
if (tokenSymbol === "WETH") {
this.logger.info(
`WETH transfer from chain ${fromChainId} to chain ${toChainId}. Will be received as ETH.`
);
}

// Execute the deposit
const txHash = await this.acrossService.executeDeposit(
fromChainId,
Expand Down

0 comments on commit 2b84dcf

Please sign in to comment.