diff --git a/config.env.yaml b/config.env.yaml index 536e67b2..6a60e409 100644 --- a/config.env.yaml +++ b/config.env.yaml @@ -30,6 +30,7 @@ gasPriceMultiplier: $GAS_PRICE_MULTIPLIER gasLimitMultiplier: $GAS_LIMIT_MULTIPLIER timeout: $TIMEOUT maxRatio: $MAX_RATIO +skipSweep: $SKIP_SWEEP rpOnly: $RP_ONLY ownerProfile: $OWNER_PROFILE selfFundVaults: $SELF_FUND_ORDERS diff --git a/config.example.yaml b/config.example.yaml index ff849e4d..ee18da17 100644 --- a/config.example.yaml +++ b/config.example.yaml @@ -103,6 +103,11 @@ timeout: 15 # Option to maximize maxIORatio, default true maxRatio: true +# list of tokens to skip sweeping +skipSweep: + - "0x123...456" + - "0xabc...def" + # Only clear orders through RP4, excludes intra and inter orderbook clears, default true rpOnly: true diff --git a/src/cli/commands/sweep.ts b/src/cli/commands/sweep.ts index 857672fb..1ee8ebee 100644 --- a/src/cli/commands/sweep.ts +++ b/src/cli/commands/sweep.ts @@ -118,6 +118,7 @@ export async function sweepFunds(opts: SweepOptions) { topupAmount: "0", contracts: {}, orderbookTradeTypes: {} as any, + skipSweep: new Set(), }; // prepare state config fields diff --git a/src/config/yaml.test.ts b/src/config/yaml.test.ts index dca76554..b007b23b 100644 --- a/src/config/yaml.test.ts +++ b/src/config/yaml.test.ts @@ -38,6 +38,9 @@ gasPriceMultiplier: 150 gasLimitMultiplier: 90 timeout: 20000 maxRatio: true +skipSweep: + - "0x8888888888888888888888888888888888888888" + - "0x9999999999999999999999999999999999999999" ownerProfile: $OWNER_PROFILE selfFundVaults: - token: "0x6666666666666666666666666666666666666666" @@ -142,6 +145,7 @@ orderbookTradeTypes: interOrderbook: new Set([`0x${"3".repeat(40)}`, `0x${"4".repeat(40)}`]), intraOrderbook: new Set([`0x${"5".repeat(40)}`, `0x${"6".repeat(40)}`]), }, + skipSweep: new Set([`0x${"8".repeat(40)}`, `0x${"9".repeat(40)}`]), }; // AppOptions returned from fromYaml() should match expected @@ -180,6 +184,7 @@ orderbookTradeTypes: gasLimitMultiplier: "90", timeout: "20000", maxRatio: true, + skipSweep: undefined, ownerProfile: [ { "0x4444444444444444444444444444444444444444": "100" }, { "0x5555555555555555555555555555555555555555": "max" }, @@ -313,5 +318,7 @@ orderbookTradeTypes: interOrderbook: new Set([`0x${"3".repeat(40)}`, `0x${"4".repeat(40)}`]), intraOrderbook: new Set([`0x${"5".repeat(40)}`, `0x${"6".repeat(40)}`]), }); + + assert.deepEqual(result.skipSweep, new Set()); }); }); diff --git a/src/config/yaml.ts b/src/config/yaml.ts index 8bc8cbae..44517ada 100644 --- a/src/config/yaml.ts +++ b/src/config/yaml.ts @@ -95,6 +95,8 @@ export type AppOptions = { contracts: AppOptionsContracts; /** Specifies enabled trade types for each orderbook address */ orderbookTradeTypes: OrderbookTradeTypes; + /** List of tokens to skip when sweeping bounty tokens */ + skipSweep: Set; }; /** Provides methods to instantiate and validate AppOptions */ @@ -261,6 +263,10 @@ export namespace AppOptions { "invalid orderbookTradeTypes.intraOrderbook, expected an array of orderbook addresses", ), }, + skipSweep: Validator.resolveAddressSet( + input.skipSweep, + "invalid skip sweep list, expected an array of token addresses", + ), } as AppOptions); } catch (error: any) { if (error instanceof AppOptionsError) { diff --git a/src/core/modes/simulator.test.ts b/src/core/modes/simulator.test.ts index eda707b7..6f3074e1 100644 --- a/src/core/modes/simulator.test.ts +++ b/src/core/modes/simulator.test.ts @@ -257,7 +257,7 @@ describe("Test TradeSimulatorBase", () => { Result.err(setTransactionDataError), ); const headroom = BigInt( - (Number(mockSolver.appOptions.gasCoveragePercentage) * 100.25).toFixed(), + (Number(mockSolver.appOptions.gasCoveragePercentage) * 100.75).toFixed(), ); const result = await mockSimulator.trySimulateTrade(); @@ -326,7 +326,7 @@ describe("Test TradeSimulatorBase", () => { }; (dryrun as Mock).mockResolvedValueOnce(Result.err(dryrunError)); const headroom = BigInt( - (Number(mockSolver.appOptions.gasCoveragePercentage) * 100.25).toFixed(), + (Number(mockSolver.appOptions.gasCoveragePercentage) * 100.75).toFixed(), ); const result = await mockSimulator.trySimulateTrade(); @@ -410,7 +410,7 @@ describe("Test TradeSimulatorBase", () => { .mockResolvedValueOnce(Result.ok(dryrunResult)) .mockResolvedValueOnce(Result.ok(dryrunResult2)); const headroom = BigInt( - (Number(mockSolver.appOptions.gasCoveragePercentage) * 100.25).toFixed(), + (Number(mockSolver.appOptions.gasCoveragePercentage) * 100.75).toFixed(), ); // last call to setTransactionData fails const setTransactionDataError = { @@ -525,7 +525,7 @@ describe("Test TradeSimulatorBase", () => { .mockResolvedValueOnce(Result.ok(dryrunResult)) .mockResolvedValueOnce(Result.ok(dryrunResult2)); const headroom = BigInt( - (Number(mockSolver.appOptions.gasCoveragePercentage) * 100.25).toFixed(), + (Number(mockSolver.appOptions.gasCoveragePercentage) * 100.75).toFixed(), ); const profitEstimate = 1234n; (mockSimulator.estimateProfit as Mock).mockReturnValueOnce(profitEstimate); diff --git a/src/core/modes/simulator.ts b/src/core/modes/simulator.ts index 00fa9de6..c81871e7 100644 --- a/src/core/modes/simulator.ts +++ b/src/core/modes/simulator.ts @@ -149,7 +149,7 @@ export abstract class TradeSimulatorBase { // determine the success of the trade with 0.25% headroom const headroom = BigInt( - (Number(this.tradeArgs.solver.appOptions.gasCoveragePercentage) * 100.25).toFixed(), + (Number(this.tradeArgs.solver.appOptions.gasCoveragePercentage) * 100.75).toFixed(), ); let minimumExpected = (estimatedGasCost * headroom) / 10000n; this.spanAttributes["gasEst.initial.minBountyExpected"] = minimumExpected.toString(); diff --git a/src/wallet/index.test.ts b/src/wallet/index.test.ts index d5bca9f0..5b4c9e98 100644 --- a/src/wallet/index.test.ts +++ b/src/wallet/index.test.ts @@ -58,6 +58,9 @@ describe("Test WalletManager", () => { getBalance: vi.fn().mockResolvedValue(0n), chain: { contracts: { multicall3: { address: "0xmulticall" } } }, }, + appOptions: { + skipSweep: new Set(), + }, } as any); multiWalletState = new SharedState({ @@ -81,6 +84,9 @@ describe("Test WalletManager", () => { getBalance: vi.fn().mockResolvedValue(0n), chain: { contracts: { multicall3: { address: "0xmulticall" } } }, }, + appOptions: { + skipSweep: new Set(), + }, } as any); workerSigner = RainSolverSigner.create( diff --git a/src/wallet/index.ts b/src/wallet/index.ts index 593a71c2..9e497c32 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -429,6 +429,11 @@ export class WalletManager { for (const [, tokenDetails] of this.state.watchedTokens) { report.setAttr(`details.swaps.${tokenDetails.symbol}.token`, tokenDetails.address); + if (this.state.appOptions.skipSweep.has(tokenDetails.address.toLowerCase())) { + report.setAttr(`details.swaps.${tokenDetails.symbol}.status`, "skipped"); + continue; + } + try { const { route = undefined,