arbi/flake.nix
Andrea Ciceri fb378c4931
Add typst and tinymist to shell
Start writing notes

Notes on Uniswap V2's optimum

Fix error in formula

Test `computeAmountInt` using various deltas

Add `concurrency` to the default configuration file

Remove unused imports

Correctly propagate error

Allow dead code

Make the priority queue a real FIFO

Refactor: remove priority queue as stream and use channels

Increase buffer size

New `flashArbitrage` function

Comment with some ideas

Add pragma version

Refactor: decrease the amount of calls

Remove unused code

Re-enable tests

Remove comment

Process known pairs when started

Avoid re-allocating a new provider every time

Ignore `nixos.qcow2` file created by the VM

Add support for `aarch64-linux`

Add NixOS module and VM configuration

Add `itertools`

Add arbitrage opportunity detection

Implement `fallback` method for non standard callbacks

Add more logs

Fix sign error in optimum formula

Add deployment scripts and `agenix-shell` secrets

Bump cargo packages

Fix typo

Print out an error if processing a pair goes wrong

Add `actionlint` to formatters

Fix typo

Add TODO comment

Remove not relevant anymore comment

Big refactor

- process actions always in the correct order avoiding corner cases
- avoid using semaphores

New API key

Add `age` to dev shell

Used by Emacs' `agenix-mode` on my system

Fix parametric deploy scripts

Add `run-forge-tests` flake app

Remove fork URL from Solidity source

Remove `pairDir` argument

Add link to `ArbitrageManager`'s ABI

WIP
2025-06-19 14:08:35 +02:00

273 lines
8.9 KiB
Nix

{
inputs = {
flake-parts.url = "github:hercules-ci/flake-parts";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
git-hooks = {
url = "github:cachix/git-hooks.nix";
inputs.nixpkgs.follows = "nixpkgs";
};
agenix-shell.url = "github:aciceri/agenix-shell";
flake-root.url = "github:srid/flake-root";
nix-github-actions = {
url = "github:nix-community/nix-github-actions";
inputs.nixpkgs.follows = "nixpkgs";
};
forge-std = {
flake = false;
url = "github:foundry-rs/forge-std/v1.9.6";
};
};
outputs = inputs:
inputs.flake-parts.lib.mkFlake { inherit inputs; } (flake@{ config, lib, moduleWithSystem, withSystem, ... }: {
systems = [ "x86_64-linux" "aarch64-linux" ];
imports = with inputs; [
git-hooks.flakeModule
treefmt-nix.flakeModule
flake-root.flakeModule
agenix-shell.flakeModules.agenix-shell
];
agenix-shell = {
secrets = {
ALCHEMY_KEY.file = ./secrets/alchemy_key.age;
WALLET_PRIVATE_KEY.file = ./secrets/wallet_private_key.age;
};
};
perSystem = { pkgs, config, ... }: {
treefmt.config = {
flakeFormatter = true;
flakeCheck = true;
programs = {
nixpkgs-fmt.enable = true;
rustfmt.enable = true;
actionlint.enable = true;
};
};
pre-commit = {
check.enable = false;
settings.hooks = {
treefmt = {
enable = true;
package = config.treefmt.build.wrapper;
};
};
};
devShells.default = pkgs.mkShell {
packages = with pkgs; [ cargo rustc rust-analyzer clippy foundry typst tinymist age ragenix ];
inputsFrom = [ config.flake-root.devShell ];
shellHook = ''
source ${lib.getExe config.agenix-shell.installationScript}
# forge will use this directory to download the solc compilers
mkdir -p $HOME/.svm
# forge needs forge-std to work
mkdir -p $FLAKE_ROOT/onchain/lib/
ln -sf ${inputs.forge-std.outPath} $FLAKE_ROOT/onchain/lib/forge-std
if [ ! -f "$FLAKE_ROOT/offchain/config.kdl" ]; then \
cp ${config.packages.arbi_sample_config_kdl} $FLAKE_ROOT/offchain/config.kdl
fi
export ARBI_CONFIG="$FLAKE_ROOT/offchain/config.kdl"
${config.pre-commit.installationScript}
'';
env = {
OPENSSL_DIR = pkgs.openssl.dev;
OPENSSL_NO_VENDOR = true;
OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib";
OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include";
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig:$PKG_CONFIG_PATH";
RUST_BACKTRACE = true;
ARBI_LOG_LEVEL = "debug";
};
};
packages = {
default = config.packages.arbi;
arbi = pkgs.rustPlatform.buildRustPackage {
pname = "arbi";
version = "0.1.0";
cargoLock.lockFile = ./offchain/Cargo.lock;
src = ./offchain;
env = {
OPENSSL_DIR = pkgs.openssl.dev;
OPENSSL_NO_VENDOR = true;
OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib";
OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include";
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig:$PKG_CONFIG_PATH";
};
meta.mainProgram = "arbi";
};
arbi_sample_config_kdl = pkgs.writeText "arbi-sample-config.kdl" ''
endpoint "wss://eth-mainnet.g.alchemy.com/v2/<REDACTED>"
pairs_file "pairs.json"
concurrency 5
'';
run-forge-tests = pkgs.writeShellScriptBin "run-forge-tests" ''
pushd "$FLAKE_ROOT/onchain"
forge test \
--fork-url "wss://mainnet.infura.io/ws/v3/$ALCHEMY_KEY" \
--via-ir \
-vvv
popd
'';
run-vm = pkgs.writeShellScriptBin "run-vm" (lib.getExe flake.config.flake.nixosConfigurations.vm.config.system.build.vm);
} // lib.genAttrs [ "polygon-mainnet" ] (network: pkgs.writeShellScriptBin "deploy-${network}" ''
pushd "$FLAKE_ROOT/onchain"
forge create \
--rpc-url "wss://${network}.infura.io/ws/v3/$ALCHEMY_KEY" \
--private-key "$WALLET_PRIVATE_KEY" \
--via-ir \
--broadcast \
src/ArbitrageManager.sol:ArbitrageManager
popd
'');
checks = {
inherit (config.packages) arbi;
};
};
flake = {
githubActions = inputs.nix-github-actions.lib.mkGithubMatrix {
checks = lib.getAttrs [ "x86_64-linux" ] config.flake.checks;
};
nixosConfigurations.vm = withSystem "x86_64-linux" (ctx: inputs.nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
({ pkgs, modulesPath, ... }: {
imports = [
"${modulesPath}/virtualisation/qemu-vm.nix"
config.flake.nixosModules.arbi
];
services.getty.autologinUser = "root";
services.openssh.settings.PasswordAuthentication = lib.mkForce true;
services.openssh.settings.PermitRootLogin = lib.mkForce "yes";
users.users.root.password = "";
virtualisation = {
graphics = false;
memorySize = 2048;
diskSize = 10000;
forwardPorts = [
{
from = "host";
host.port = 2222;
guest.port = 22;
}
];
};
system.stateVersion = "25.05";
services.arbi = {
enable = true;
log_level = "debug";
configFile = pkgs.writeText "arbi-config.kdl" ''
endpoint "wss://eth-mainnet.g.alchemy.com/v2/kkDMaLVYpWQA0GsCYNFvAODnAxCCiamv"
pairs_file "pairs.json"
concurrency 5
'';
};
})
];
});
nixosModules = {
arbi = moduleWithSystem ({ config }: nixos@{ lib, utils, ... }:
let
cfg = nixos.config.services.arbi;
in
{
options.services.arbi = {
enable = lib.mkEnableOption "arbi";
package = lib.mkOption {
type = lib.types.package;
default = config.packages.arbi;
};
log_level = lib.mkOption {
type = lib.types.enum [ "debug" "trace" "warn" "error" "info" ];
default = "info";
};
configFile = lib.mkOption {
type = lib.types.path;
};
dataDir = lib.mkOption {
type = lib.types.path;
default = "/var/lib/arbi";
};
user = lib.mkOption {
type = lib.types.str;
default = "arbi";
};
group = lib.mkOption {
type = lib.types.str;
default = "arbi";
};
};
config = lib.mkIf cfg.enable {
environment.systemPackages = [ cfg.package ];
users.users.arbi = lib.mkIf (cfg.user == "arbi") {
isSystemUser = true;
group = cfg.group;
};
users.groups.arbi = lib.mkIf (cfg.group == "arbi") { };
systemd.tmpfiles.settings."10-arbi" = {
${cfg.dataDir}.d = {
inherit (cfg) user group;
mode = "0755";
};
};
systemd.services.arbi = {
description = "Arbitrage bot";
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
environment.ARBI_LOG_LEVEL = cfg.log_level;
serviceConfig = {
ExecStart = utils.escapeSystemdExecArgs [
(lib.getExe cfg.package)
"--config"
cfg.configFile
"run"
];
KillSignal = "SIGINT";
Restart = "on-failure";
RestartSec = "5s";
User = cfg.user;
Group = cfg.group;
WorkingDirectory = cfg.dataDir;
UMask = "0022";
};
};
};
});
default = config.flake.nixosModules.arbi;
};
};
});
}