From 2b84dcff81d6a6e51a08a26fcc72191cdc4a05bf Mon Sep 17 00:00:00 2001 From: 0age <37939117+0age@users.noreply.github.com> Date: Mon, 3 Mar 2025 13:53:03 -0500 Subject: [PATCH] skip WETH rebalancing --- src/server/config/rebalance.ts | 2 +- src/server/services/across/AcrossService.ts | 59 +++++++++++----- src/server/services/across/BalanceAnalyzer.ts | 8 +++ .../services/across/RebalanceDecisionMaker.ts | 68 ++++++++++++++++--- .../services/across/RebalanceService.ts | 11 ++- 5 files changed, 117 insertions(+), 31 deletions(-) diff --git a/src/server/config/rebalance.ts b/src/server/config/rebalance.ts index 732212b..1e88758 100644 --- a/src/server/config/rebalance.ts +++ b/src/server/config/rebalance.ts @@ -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 diff --git a/src/server/services/across/AcrossService.ts b/src/server/services/across/AcrossService.ts index 5155bfd..4e0994e 100644 --- a/src/server/services/across/AcrossService.ts +++ b/src/server/services/across/AcrossService.ts @@ -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); @@ -111,7 +129,7 @@ export class AcrossService { depositor, recipient: recipient || depositor, inputToken, - outputToken, + outputToken: finalOutputToken, inputAmount: inputAmount.toString(), outputAmount: outputAmount.toString(), destinationChainId, @@ -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, @@ -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({ diff --git a/src/server/services/across/BalanceAnalyzer.ts b/src/server/services/across/BalanceAnalyzer.ts index 65858b5..832c2a4 100644 --- a/src/server/services/across/BalanceAnalyzer.ts +++ b/src/server/services/across/BalanceAnalyzer.ts @@ -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}`, diff --git a/src/server/services/across/RebalanceDecisionMaker.ts b/src/server/services/across/RebalanceDecisionMaker.ts index baf40c4..c80a403 100644 --- a/src/server/services/across/RebalanceDecisionMaker.ts +++ b/src/server/services/across/RebalanceDecisionMaker.ts @@ -32,6 +32,7 @@ export class RebalanceDecisionMaker { /** * Select the token to use for rebalancing + * Never selects WETH for rebalancing */ public selectTokenToRebalance( sourceChain: { @@ -58,10 +59,15 @@ 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, @@ -69,6 +75,36 @@ export class RebalanceDecisionMaker { })) ); + // 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 @@ -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 ); @@ -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; + } } } diff --git a/src/server/services/across/RebalanceService.ts b/src/server/services/across/RebalanceService.ts index b81e487..c62b3f8 100644 --- a/src/server/services/across/RebalanceService.ts +++ b/src/server/services/across/RebalanceService.ts @@ -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,