Skip to content
Open
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
12 changes: 12 additions & 0 deletions src/config-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ export interface UniswapV3Overrides {
uniswapV3Router?: string;
}

export interface UniversalRouterOverrides {
universalRouterAddress?: string;
wethAddress?: string;
permit2Address?: string;
defaultFeeTier?: number;
defaultSlippage?: number;
poolFactoryAddress?: string;
}


export interface KeeperConfig {
/** The url of RPC endpoint. Should include API key. example: https://avax-mainnet.g.alchemy.com/v2/asf... */
ethRpcUrl: string;
Expand Down Expand Up @@ -208,6 +218,8 @@ export interface KeeperConfig {
tokenAddresses?: { [tokenSymbol: string]: string };
/** Optional list of token addresses used as intermediate connectors in 1inch swap routes */
connectorTokens?: Array<string>;
/** Uniswap Universal Router for Uni v3 */
universalRouterOverrides?: UniversalRouterOverrides;
}

export async function readConfigFile(filePath: string): Promise<KeeperConfig> {
Expand Down
60 changes: 48 additions & 12 deletions src/dex-router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { approveErc20, getAllowanceOfErc20, getDecimalsErc20 } from './erc20';
import { logger } from './logging';
import { swapToWeth } from './uniswap';
import { tokenChangeDecimals } from './utils';
import { swapWithUniversalRouter } from './universal-router-module';

// TODO:
// Why does this log errors and return failure rather than throwing exceptions?
Expand Down Expand Up @@ -255,16 +256,24 @@ export class DexRouter {
}

public async swap(
chainId: number,
amount: BigNumber,
tokenIn: string,
tokenOut: string,
to: string,
useOneInch: boolean,
slippage: number = 1,
feeAmount: number = 3000,
uniswapOverrides?: { wethAddress?: string; uniswapV3Router?: string }
): Promise<{ success: boolean; error?: string }> {
chainId: number,
amount: BigNumber,
tokenIn: string,
tokenOut: string,
to: string,
useOneInch: boolean,
slippage: number = 1,
feeAmount: number = 3000,
uniswapOverrides?: {
wethAddress?: string;
uniswapV3Router?: string;
universalRouterAddress?: string;
permit2Address?: string;
poolFactoryAddress?: string;
defaultFeeTier?: number;
defaultSlippage?: number;
}
): Promise<{ success: boolean; error?: string }> {
if (!chainId || !amount || !tokenIn || !tokenOut || !to) {
logger.error('Invalid parameters provided to swap');
return { success: false, error: 'Invalid parameters provided to swap' };
Expand Down Expand Up @@ -294,8 +303,8 @@ export class DexRouter {
return { success: false, error: `Insufficient balance for ${tokenIn}` };
}

const effectiveUseOneInch = chainId === 43114 ? true : useOneInch;
if (effectiveUseOneInch) {

if (useOneInch) {
const oneInchRouter = this.oneInchRouters[chainId];
if (!oneInchRouter) {
logger.error(`No 1inch router defined for chainId ${chainId}`);
Expand Down Expand Up @@ -342,6 +351,32 @@ export class DexRouter {
);
return result;
} else {
// Check if we have Universal Router settings
if (uniswapOverrides?.universalRouterAddress && uniswapOverrides?.permit2Address && uniswapOverrides?.poolFactoryAddress) {
try {
logger.info(`Using Universal Router for swap`);
// Use the Universal Router if configuration is available
await swapWithUniversalRouter(
this.signer,
tokenIn,
adjustedAmount,
tokenOut,
slippage * 100, // Convert percentage to basis points
uniswapOverrides.universalRouterAddress,
uniswapOverrides.permit2Address,
uniswapOverrides.defaultFeeTier || feeAmount,
uniswapOverrides.poolFactoryAddress
);
logger.info(
`Universal Router swap successful: ${adjustedAmount.toString()} ${tokenIn} -> ${tokenOut}`
);
return { success: true };
} catch (error) {
logger.error(`Universal Router swap failed for token: ${tokenIn}: ${error}`);
return { success: false, error: `Universal Router swap failed: ${error}` };
}
} else {

try {
await swapToWeth(
this.signer,
Expand All @@ -361,3 +396,4 @@ export class DexRouter {
}
}
}
}
82 changes: 68 additions & 14 deletions src/nonce.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Updated nonce.ts with simpler promise handling
import { Signer } from 'ethers';
import { logger } from './logging';

export class NonceTracker {
private nonces: Map<string, Promise<number>> = new Map();
private nonces: Map<string, number> = new Map();
private pendingTransactions: Map<string, Promise<void>> = new Map();
private static instance: NonceTracker;

constructor() {
Expand All @@ -18,29 +21,80 @@ export class NonceTracker {

static async resetNonce(signer: Signer, address: string) {
const tracker = new NonceTracker();
await tracker.resetNonce(signer, address);
return await tracker.resetNonce(signer, address);
}

static clearNonces() {
const tracker = new NonceTracker();
tracker.nonces = new Map();
tracker.pendingTransactions = new Map();
logger.debug('Cleared all nonce tracking data');
}

static async queueTransaction<T>(
signer: Signer,
txFunction: (nonce: number) => Promise<T>
): Promise<T> {
const tracker = new NonceTracker();
return tracker.queueTransaction(signer, txFunction);
}

public async getNonce(signer: Signer): Promise<number> {
const address = await signer.getAddress();
if (!this.nonces.get(address)) {
this.resetNonce(signer, address);
logger.debug(`Getting nonce for address: ${address}`);

// If we don't have a nonce stored, get it from the network
if (this.nonces.get(address) === undefined) {
await this.resetNonce(signer, address);
}
const currNonce = this.nonces.get(address)!;
this.nonces.set(address, incrementNonce(currNonce));
return currNonce;

// Get the current nonce value
const currentNonce = this.nonces.get(address)!;
logger.debug(`Using nonce: ${currentNonce}`);

// Increment the stored nonce for next time
this.nonces.set(address, currentNonce + 1);

return currentNonce;
}

public resetNonce(signer: Signer, address: string) {
this.nonces.set(address, signer.getTransactionCount('pending'));

public async resetNonce(signer: Signer, address: string) {
// Get the latest nonce directly from the network
const latestNonce = await signer.getTransactionCount('pending');
logger.debug(`Reset nonce for ${address} to ${latestNonce}`);
this.nonces.set(address, latestNonce);
return latestNonce;
}

// Simplified implementation that focuses just on managing nonces correctly
public async queueTransaction<T>(
signer: Signer,
txFunction: (nonce: number) => Promise<T>
): Promise<T> {
const address = await signer.getAddress();
logger.debug(`Queueing transaction for ${address}`);

try {
// Get nonce from our tracker
const nonce = await this.getNonce(signer);
logger.debug(`Executing transaction with nonce ${nonce}`);

// Execute the transaction
try {
const result = await txFunction(nonce);
logger.debug(`Transaction with nonce ${nonce} completed successfully`);
return result;
} catch (txError) {
logger.error(`Transaction with nonce ${nonce} failed: ${txError}`);

// Reset nonce on failure
await this.resetNonce(signer, address);

throw txError;
}
} catch (error) {
logger.error(`Error in queueTransaction: ${error}`);
throw error;
}
}
}

async function incrementNonce(nonce: Promise<number>) {
return (await nonce) + 1;
}
Loading