Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
rouzwelt committed Nov 16, 2024
1 parent e72cf2f commit bddf02e
Show file tree
Hide file tree
Showing 18 changed files with 363 additions and 55 deletions.
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ Other optional arguments are:
- `-w` or `--wallet-count`, Number of wallet to submit transactions with, requirs `--mnemonic`. Will override the 'WALLET_COUNT' in env variables
- `-t` or `--topup-amount`, The initial topup amount of excess wallets, requirs `--mnemonic`. Will override the 'TOPUP_AMOUNT' in env variables
- `--gas-price-multiplier`, Option to multiply the gas price fetched from the rpc as percentage, default is 107, ie +7%. Will override the 'GAS_PRICE_MULTIPLIER' in env variables
- `--gas-limit-multiplier`, Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5%. Will override the 'GAS_LIMIT_MULTIPLIER' in env variables
- `-V` or `--version`, output the version number
- `-h` or `--help`, output usage information

Expand Down Expand Up @@ -256,6 +257,9 @@ ROUTE="single"

# Option to multiply the gas price fetched from the rpc as percentage, default is 107, ie +7%
GAS_PRICE_MULTIPLIER=

# Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5%
GAS_LIMIT_MULTIPLIER=
```
If both env variables and CLI argument are set, the CLI arguments will be prioritized and override the env variables.

Expand Down
3 changes: 3 additions & 0 deletions example.env
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ ROUTE="single"
# Option to multiply the gas price fetched from the rpc as percentage, default is 107, ie +7%
GAS_PRICE_MULTIPLIER=

# Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5%
GAS_LIMIT_MULTIPLIER=


# test rpcs vars
TEST_POLYGON_RPC=
Expand Down
66 changes: 58 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ const ENV_OPTIONS = {
botMinBalance: process?.env?.BOT_MIN_BALANCE,
selfFundOrders: process?.env?.SELF_FUND_ORDERS,
gasPriceMultiplier: process?.env?.GAS_PRICE_MULTIPLIER,
gasLimitMultiplier: process?.env?.GAS_LIMIT_MULTIPLIER,
txGas: process?.env?.TX_GAS,
route: process?.env?.ROUTE,
rpc: process?.env?.RPC_URL
? Array.from(process?.env?.RPC_URL.matchAll(/[^,\s]+/g)).map((v) => v[0])
Expand Down Expand Up @@ -168,6 +170,14 @@ const getOptions = async (argv: any, version?: string) => {
"--gas-price-multiplier <integer>",
"Option to multiply the gas price fetched from the rpc as percentage, default is 107, ie +7%. Will override the 'GAS_PRICE_MULTIPLIER' in env variables",
)
.option(
"--gas-limit-multiplier <integer>",
"Option to multiply the gas limit estimation from the rpc as percentage, default is 105, ie +5%. Will override the 'GAS_LIMIT_MULTIPLIER' in env variables",
)
.option(
"--tx-gas <integer>",
"Option to set a static gas limit for all submitting txs. Will override the 'TX_GAS' in env variables",
)
.description(
[
"A NodeJS app to find and take arbitrage trades for Rain Orderbook orders against some DeFi liquidity providers, requires NodeJS v18 or higher.",
Expand Down Expand Up @@ -203,6 +213,8 @@ const getOptions = async (argv: any, version?: string) => {
cmdOptions.topupAmount = cmdOptions.topupAmount || ENV_OPTIONS.topupAmount;
cmdOptions.selfFundOrders = cmdOptions.selfFundOrders || ENV_OPTIONS.selfFundOrders;
cmdOptions.gasPriceMultiplier = cmdOptions.gasPriceMultiplier || ENV_OPTIONS.gasPriceMultiplier;
cmdOptions.gasLimitMultiplier = cmdOptions.gasLimitMultiplier || ENV_OPTIONS.gasLimitMultiplier;
cmdOptions.txGas = cmdOptions.txGas || ENV_OPTIONS.txGas;
cmdOptions.botMinBalance = cmdOptions.botMinBalance || ENV_OPTIONS.botMinBalance;
cmdOptions.route = cmdOptions.route || ENV_OPTIONS.route;
cmdOptions.bundle = cmdOptions.bundle ? ENV_OPTIONS.bundle : false;
Expand Down Expand Up @@ -251,7 +263,7 @@ export const arbRound = async (
if (!ordersDetails.length) {
span.setStatus({ code: SpanStatusCode.OK, message: "found no orders" });
span.end();
return { txs: [], foundOpp: false, avgGasCost: undefined };
return { txs: [], foundOpp: false, didClear: false, avgGasCost: undefined };
}
} catch (e: any) {
const snapshot = errorSnapshot("", e);
Expand All @@ -260,12 +272,13 @@ export const arbRound = async (
span.setAttribute("didClear", false);
span.setAttribute("foundOpp", false);
span.end();
return { txs: [], foundOpp: false, avgGasCost: undefined };
return { txs: [], foundOpp: false, didClear: false, avgGasCost: undefined };
}

try {
let txs;
let foundOpp = false;
let didClear = false;
const { reports = [], avgGasCost = undefined } = await clear(
config,
ordersDetails,
Expand All @@ -277,14 +290,22 @@ export const arbRound = async (
if (txs.length) {
foundOpp = true;
span.setAttribute("txUrls", txs);
span.setAttribute("didClear", true);
span.setAttribute("foundOpp", true);
} else if (
reports.some((v) => v.status === ProcessPairReportStatus.FoundOpportunity)
) {
foundOpp = true;
span.setAttribute("foundOpp", true);
}
if (
reports.some(
(v) =>
v.status === ProcessPairReportStatus.FoundOpportunity && !v.reason,
)
) {
didClear = true;
span.setAttribute("didClear", true);
}
} else {
span.setAttribute("didClear", false);
}
Expand All @@ -293,7 +314,7 @@ export const arbRound = async (
}
span.setStatus({ code: SpanStatusCode.OK });
span.end();
return { txs, foundOpp, avgGasCost };
return { txs, foundOpp, didClear, avgGasCost };
} catch (e: any) {
if (e?.startsWith?.("Failed to batch quote orders")) {
span.setAttribute("severity", ErrorSeverity.LOW);
Expand All @@ -307,7 +328,7 @@ export const arbRound = async (
span.setAttribute("didClear", false);
span.setAttribute("foundOpp", false);
span.end();
return { txs: [], foundOpp: false, avgGasCost: undefined };
return { txs: [], foundOpp: false, didClear: false, avgGasCost: undefined };
}
} catch (e: any) {
const snapshot = errorSnapshot("Unexpected error occured", e);
Expand All @@ -317,7 +338,7 @@ export const arbRound = async (
span.setAttribute("didClear", false);
span.setAttribute("foundOpp", false);
span.end();
return { txs: [], foundOpp: false, avgGasCost: undefined };
return { txs: [], foundOpp: false, didClear: false, avgGasCost: undefined };
}
});
};
Expand Down Expand Up @@ -389,6 +410,32 @@ export async function startup(argv: any, version?: string, tracer?: Tracer, ctx?
} else {
options.gasPriceMultiplier = 107;
}
if (options.gasLimitMultiplier) {
if (typeof options.gasLimitMultiplier === "number") {
if (options.gasLimitMultiplier <= 0 || !Number.isInteger(options.gasLimitMultiplier))
throw "invalid gasLimitMultiplier value, must be an integer greater than zero";
} else if (
typeof options.gasLimitMultiplier === "string" &&
/^[0-9]+$/.test(options.gasLimitMultiplier)
) {
options.gasLimitMultiplier = Number(options.gasLimitMultiplier);
if (options.gasLimitMultiplier <= 0)
throw "invalid gasLimitMultiplier value, must be an integer greater than zero";
} else throw "invalid gasLimitMultiplier value, must be an integer greater than zero";
} else {
options.gasLimitMultiplier = 105;
}
if (options.txGas) {
if (typeof options.txGas === "number") {
if (options.txGas <= 0 || !Number.isInteger(options.txGas))
throw "invalid txGas value, must be an integer greater than zero";
else options.txGas = BigInt(options.txGas);
} else if (typeof options.txGas === "string" && /^[0-9]+$/.test(options.txGas)) {
options.txGas = BigInt(options.txGas);
if (options.txGas <= 0n)
throw "invalid txGas value, must be an integer greater than zero";
} else throw "invalid txGas value, must be an integer greater than zero";
}
const poolUpdateInterval = _poolUpdateInterval * 60 * 1000;
let ordersDetails: any[] = [];
if (!process?.env?.TEST)
Expand Down Expand Up @@ -559,16 +606,19 @@ export const main = async (argv: any, version?: string) => {
try {
await rotateProviders(config, update);
const roundResult = await arbRound(tracer, roundCtx, options, config);
let txs, foundOpp, roundAvgGasCost;
let txs, foundOpp, didClear, roundAvgGasCost;
if (roundResult) {
txs = roundResult.txs;
foundOpp = roundResult.foundOpp;
didClear = roundResult.didClear;
roundAvgGasCost = roundResult.avgGasCost;
}
if (txs && txs.length) {
roundSpan.setAttribute("txUrls", txs);
roundSpan.setAttribute("didClear", true);
roundSpan.setAttribute("foundOpp", true);
} else if (didClear) {
roundSpan.setAttribute("foundOpp", true);
roundSpan.setAttribute("didClear", true);
} else if (foundOpp) {
roundSpan.setAttribute("foundOpp", true);
roundSpan.setAttribute("didClear", false);
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ export async function getConfig(
config.route = route;
config.rpcRecords = rpcRecords;
config.gasPriceMultiplier = options.gasPriceMultiplier;
config.gasLimitMultiplier = options.gasLimitMultiplier;

// init accounts
const { mainAccount, accounts } = await initAccounts(walletKey, config, options, tracer, ctx);
Expand Down
6 changes: 6 additions & 0 deletions src/modes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ export async function findOpp({

if (allResults.some((v) => v.status === "fulfilled")) {
// pick and return the highest profit
allResults.forEach((v, i) => {
if (v.status === "fulfilled") {
v.value.spanAttributes["clearModePick"] =
i === 0 ? "rp4" : i === 1 ? "intra" : "inter";
}
});
const res = allResults.filter(
(v) => v.status === "fulfilled",
) as PromiseFulfilledResult<DryrunResult>[];
Expand Down
20 changes: 17 additions & 3 deletions src/modes/interOrderbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,14 @@ export async function dryrun({
evaluable: {
interpreter: orderPairObject.takeOrders[0].takeOrder.order.evaluable.interpreter,
store: orderPairObject.takeOrders[0].takeOrder.order.evaluable.store,
bytecode: "0x",
bytecode:
config.gasCoveragePercentage === "0"
? "0x"
: getBountyEnsureBytecode(
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
ethers.constants.Zero,
),
},
signedContext: [],
};
Expand All @@ -102,7 +109,9 @@ export async function dryrun({
try {
blockNumber = Number(await viemClient.getBlockNumber());
spanAttributes["blockNumber"] = blockNumber;
gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx));
gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx))
.mul(config.gasLimitMultiplier)
.div(100);
} catch (e) {
const isNodeError = containsNodeError(e as BaseError);
const errMsg = errorSnapshot("", e);
Expand Down Expand Up @@ -144,7 +153,9 @@ export async function dryrun({
try {
blockNumber = Number(await viemClient.getBlockNumber());
spanAttributes["blockNumber"] = blockNumber;
gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx));
gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx))
.mul(config.gasLimitMultiplier)
.div(100);
rawtx.gas = gasLimit.toBigInt();
gasCost = gasLimit.mul(gasPrice);
task.evaluable.bytecode = getBountyEnsureBytecode(
Expand Down Expand Up @@ -179,6 +190,9 @@ export async function dryrun({
}
}
rawtx.gas = gasLimit.toBigInt();
if (typeof config.txGas === "bigint") {
rawtx.gas = config.txGas;
}

// if reached here, it means there was a success and found opp
spanAttributes["oppBlockNumber"] = blockNumber;
Expand Down
57 changes: 37 additions & 20 deletions src/modes/intraOrderbook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ export async function dryrun({
const inputBountyVaultId = "1";
const outputBountyVaultId = "1";
const obInterface = new ethers.utils.Interface(orderbookAbi);
const task = {
evaluable: {
interpreter: orderPairObject.takeOrders[0].takeOrder.order.evaluable.interpreter,
store: orderPairObject.takeOrders[0].takeOrder.order.evaluable.store,
bytecode: getWithdrawEnsureBytecode(
signer.account.address,
orderPairObject.buyToken,
orderPairObject.sellToken,
inputBalance,
outputBalance,
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
ethers.constants.Zero,
),
},
signedContext: [],
};
const withdrawInputCalldata = obInterface.encodeFunctionData("withdraw2", [
orderPairObject.buyToken,
inputBountyVaultId,
Expand All @@ -59,7 +76,7 @@ export async function dryrun({
orderPairObject.sellToken,
outputBountyVaultId,
ethers.constants.MaxUint256,
[],
config.gasCoveragePercentage === "0" ? [] : [task],
]);
const clear2Calldata = obInterface.encodeFunctionData("clear2", [
orderPairObject.takeOrders[0].takeOrder.order,
Expand Down Expand Up @@ -89,7 +106,9 @@ export async function dryrun({
try {
blockNumber = Number(await viemClient.getBlockNumber());
spanAttributes["blockNumber"] = blockNumber;
gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx));
gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx))
.mul(config.gasLimitMultiplier)
.div(100);
} catch (e) {
// reason, code, method, transaction, error, stack, message
const isNodeError = containsNodeError(e as BaseError);
Expand Down Expand Up @@ -118,23 +137,16 @@ export async function dryrun({
// sender output which is already called above
if (config.gasCoveragePercentage !== "0") {
const headroom = (Number(config.gasCoveragePercentage) * 1.03).toFixed();
const task = {
evaluable: {
interpreter: orderPairObject.takeOrders[0].takeOrder.order.evaluable.interpreter,
store: orderPairObject.takeOrders[0].takeOrder.order.evaluable.store,
bytecode: getWithdrawEnsureBytecode(
signer.account.address,
orderPairObject.buyToken,
orderPairObject.sellToken,
inputBalance,
outputBalance,
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
gasCost.mul(headroom).div("100"),
),
},
signedContext: [],
};
task.evaluable.bytecode = getWithdrawEnsureBytecode(
signer.account.address,
orderPairObject.buyToken,
orderPairObject.sellToken,
inputBalance,
outputBalance,
ethers.utils.parseUnits(inputToEthPrice),
ethers.utils.parseUnits(outputToEthPrice),
gasCost.mul(headroom).div("100"),
);
withdrawOutputCalldata = obInterface.encodeFunctionData("withdraw2", [
orderPairObject.sellToken,
outputBountyVaultId,
Expand All @@ -148,7 +160,9 @@ export async function dryrun({
try {
blockNumber = Number(await viemClient.getBlockNumber());
spanAttributes["blockNumber"] = blockNumber;
gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx));
gasLimit = ethers.BigNumber.from(await signer.estimateGas(rawtx))
.mul(config.gasLimitMultiplier)
.div(100);
rawtx.gas = gasLimit.toBigInt();
gasCost = gasLimit.mul(gasPrice);
task.evaluable.bytecode = getWithdrawEnsureBytecode(
Expand Down Expand Up @@ -192,6 +206,9 @@ export async function dryrun({
}
}
rawtx.gas = gasLimit.toBigInt();
if (typeof config.txGas === "bigint") {
rawtx.gas = config.txGas;
}

// if reached here, it means there was a success and found opp
spanAttributes["oppBlockNumber"] = blockNumber;
Expand Down
Loading

0 comments on commit bddf02e

Please sign in to comment.