commit db85f91cb1bc12ec0e3633b3c6343b466a0ce1ed
parent fd270e7817f6fb50ae5d86bd30fd871cd786a30d
Author: miksa234 <milutin@popovic.xyz>
Date: Fri, 25 Apr 2025 20:07:19 +0100
inital
Diffstat:
25 files changed, 8499 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,362 @@
+# C extensions
+*.so
+
+
+# Model & Data
+data
+model
+
+# Packages
+*.egg
+*.egg-info
+bootstrap_scss
+migrations
+dist
+build
+eggs
+parts
+bin
+var
+sdist
+develop-eggs
+.installed.cfg
+lib
+lib64
+__pycache__
+.ipynb_checkpoints
+
+# Installer logs
+pip-log.txt
+
+# Unit test / coverage reports
+.coverage
+htmlcov
+.tox
+nosetests.xml
+test.db
+
+# Translations
+*.pot
+
+# Mr Developer
+venv
+src/app.db
+src/.env
+logs
+*log
+images
+.env
+.vimspector.json
+
+
+# Added by cargo
+/target
+target
+
+## Core latex/pdflatex auxiliary files:
+*.aux
+*.lof
+*.log
+*.lot
+*.fls
+*.out
+*.toc
+*.fmt
+*.fot
+*.cb
+*.cb2
+.*.lb
+
+## Intermediate documents:
+*.dvi
+*.xdv
+*-converted-to.*
+# these rules might exclude image files for figures etc.
+# *.ps
+# *.eps
+# *.pdf
+
+## Generated if empty string is given at "Please type another file name for output:"
+.pdf
+
+## Bibliography auxiliary files (bibtex/biblatex/biber):
+*.bbl
+*.bbl-SAVE-ERROR
+*.bcf
+*.blg
+*-blx.aux
+*-blx.bib
+*.run.xml
+
+## Build tool auxiliary files:
+*.fdb_latexmk
+*.synctex
+*.synctex(busy)
+*.synctex.gz
+*.synctex.gz(busy)
+*.pdfsync
+*.rubbercache
+rubber.cache
+
+## Build tool directories for auxiliary files
+# latexrun
+latex.out/
+
+## Auxiliary and intermediate files from other packages:
+# algorithms
+*.alg
+*.loa
+
+# achemso
+acs-*.bib
+
+# amsthm
+*.thm
+
+# beamer
+*.nav
+*.pre
+*.snm
+*.vrb
+
+# changes
+*.soc
+
+# comment
+*.cut
+
+# cprotect
+*.cpt
+
+# elsarticle (documentclass of Elsevier journals)
+*.spl
+
+# endnotes
+*.ent
+
+# fixme
+*.lox
+
+# feynmf/feynmp
+*.mf
+*.mp
+*.t[1-9]
+*.t[1-9][0-9]
+*.tfm
+
+#(r)(e)ledmac/(r)(e)ledpar
+*.end
+*.?end
+*.[1-9]
+*.[1-9][0-9]
+*.[1-9][0-9][0-9]
+*.[1-9]R
+*.[1-9][0-9]R
+*.[1-9][0-9][0-9]R
+*.eledsec[1-9]
+*.eledsec[1-9]R
+*.eledsec[1-9][0-9]
+*.eledsec[1-9][0-9]R
+*.eledsec[1-9][0-9][0-9]
+*.eledsec[1-9][0-9][0-9]R
+
+# glossaries
+*.acn
+*.acr
+*.glg
+*.glo
+*.gls
+*.glsdefs
+*.lzo
+*.lzs
+*.slg
+*.slo
+*.sls
+
+# uncomment this for glossaries-extra (will ignore makeindex's style files!)
+# *.ist
+
+# gnuplot
+*.gnuplot
+*.table
+
+# gnuplottex
+*-gnuplottex-*
+
+# gregoriotex
+*.gaux
+*.glog
+*.gtex
+
+# htlatex
+*.4ct
+*.4tc
+*.idv
+*.lg
+*.trc
+*.xref
+
+# hypdoc
+*.hd
+
+# hyperref
+*.brf
+
+# knitr
+*-concordance.tex
+# TODO Uncomment the next line if you use knitr and want to ignore its generated tikz files
+# *.tikz
+*-tikzDictionary
+
+# listings
+*.lol
+
+# luatexja-ruby
+*.ltjruby
+
+# makeidx
+*.idx
+*.ilg
+*.ind
+
+# minitoc
+*.maf
+*.mlf
+*.mlt
+*.mtc[0-9]*
+*.slf[0-9]*
+*.slt[0-9]*
+*.stc[0-9]*
+
+# minted
+_minted*
+*.pyg
+
+# morewrites
+*.mw
+
+# newpax
+*.newpax
+
+# nomencl
+*.nlg
+*.nlo
+*.nls
+
+# pax
+*.pax
+
+# pdfpcnotes
+*.pdfpc
+
+# sagetex
+*.sagetex.sage
+*.sagetex.py
+*.sagetex.scmd
+
+# scrwfile
+*.wrt
+
+# svg
+svg-inkscape/
+
+# sympy
+*.sout
+*.sympy
+sympy-plots-for-*.tex/
+
+# pdfcomment
+*.upa
+*.upb
+
+# pythontex
+*.pytxcode
+pythontex-files-*/
+
+# tcolorbox
+*.listing
+
+# thmtools
+*.loe
+
+# TikZ & PGF
+*.dpth
+*.md5
+*.auxlock
+
+# titletoc
+*.ptc
+
+# todonotes
+*.tdo
+
+# vhistory
+*.hst
+*.ver
+
+# easy-todo
+*.lod
+
+# xcolor
+*.xcp
+
+# xmpincl
+*.xmpi
+
+# xindy
+*.xdy
+
+# xypic precompiled matrices and outlines
+*.xyc
+*.xyd
+
+# endfloat
+*.ttt
+*.fff
+
+# Latexian
+TSWLatexianTemp*
+
+## Editors:
+# WinEdt
+*.bak
+*.sav
+
+# Texpad
+.texpadtmp
+
+# LyX
+*.lyx~
+
+# Kile
+*.backup
+
+# gummi
+.*.swp
+
+# KBibTeX
+*~[0-9]*
+
+# TeXnicCenter
+*.tps
+
+# auto folder when using emacs and auctex
+./auto/*
+*.el
+
+# expex forward references with \gathertags
+*-tags.tex
+
+# standalone packages
+*.sta
+
+# Makeindex log files
+*.lpz
+
+# xwatermark package
+*.xwm
+
+# REVTeX puts footnotes in the bibliography by default, unless the nofootinbib
+# option is specified. Footnotes are the stored in a file with suffix Notes.bib.
+# Uncomment the next line to have this generated file ignored.
+#*Notes.bib
diff --git a/README.md b/README.md
@@ -0,0 +1,3 @@
+N-Cyclic Arbitrage on Constant Function Market Makers
+
+Master Thesis.
diff --git a/block_extractor/Cargo.lock b/block_extractor/Cargo.lock
@@ -0,0 +1,4598 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "addr2line"
+version = "0.24.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
+dependencies = [
+ "gimli",
+]
+
+[[package]]
+name = "adler2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "const-random",
+ "getrandom",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "alloc-no-stdlib"
+version = "2.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3"
+
+[[package]]
+name = "alloc-stdlib"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece"
+dependencies = [
+ "alloc-no-stdlib",
+]
+
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
+[[package]]
+name = "alloy"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbcc41e8a11a4975b18ec6afba2cc48d591fa63336a4c526dacb50479a8d6b35"
+dependencies = [
+ "alloy-consensus",
+ "alloy-contract",
+ "alloy-core",
+ "alloy-eips",
+ "alloy-genesis",
+ "alloy-network",
+ "alloy-provider",
+ "alloy-pubsub",
+ "alloy-rpc-client",
+ "alloy-rpc-types",
+ "alloy-serde",
+ "alloy-signer",
+ "alloy-signer-local",
+ "alloy-transport",
+ "alloy-transport-http",
+ "alloy-transport-ipc",
+ "alloy-transport-ws",
+]
+
+[[package]]
+name = "alloy-chains"
+version = "0.1.57"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ab9d1367c6ffb90c93fb4a9a4989530aa85112438c6f73a734067255d348469"
+dependencies = [
+ "alloy-primitives",
+ "num_enum",
+ "strum",
+]
+
+[[package]]
+name = "alloy-consensus"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4138dc275554afa6f18c4217262ac9388790b2fc393c2dfe03c51d357abf013"
+dependencies = [
+ "alloy-eips",
+ "alloy-primitives",
+ "alloy-rlp",
+ "alloy-serde",
+ "alloy-trie",
+ "auto_impl",
+ "c-kzg",
+ "derive_more",
+ "k256",
+ "serde",
+]
+
+[[package]]
+name = "alloy-consensus-any"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa04e1882c31288ce1028fdf31b6ea94cfa9eafa2e497f903ded631c8c6a42c"
+dependencies = [
+ "alloy-consensus",
+ "alloy-eips",
+ "alloy-primitives",
+ "alloy-rlp",
+ "alloy-serde",
+ "serde",
+]
+
+[[package]]
+name = "alloy-contract"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f21886c1fea0626f755a49b2ac653b396fb345233f6170db2da3d0ada31560c"
+dependencies = [
+ "alloy-dyn-abi",
+ "alloy-json-abi",
+ "alloy-network",
+ "alloy-network-primitives",
+ "alloy-primitives",
+ "alloy-provider",
+ "alloy-pubsub",
+ "alloy-rpc-types-eth",
+ "alloy-sol-types",
+ "alloy-transport",
+ "futures",
+ "futures-util",
+ "thiserror 2.0.11",
+]
+
+[[package]]
+name = "alloy-core"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "648275bb59110f88cc5fa9a176845e52a554ebfebac2d21220bcda8c9220f797"
+dependencies = [
+ "alloy-dyn-abi",
+ "alloy-json-abi",
+ "alloy-primitives",
+ "alloy-rlp",
+ "alloy-sol-types",
+]
+
+[[package]]
+name = "alloy-dyn-abi"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc9138f4f0912793642d453523c3116bd5d9e11de73b70177aa7cb3e94b98ad2"
+dependencies = [
+ "alloy-json-abi",
+ "alloy-primitives",
+ "alloy-sol-type-parser",
+ "alloy-sol-types",
+ "const-hex",
+ "itoa",
+ "serde",
+ "serde_json",
+ "winnow",
+]
+
+[[package]]
+name = "alloy-eip2930"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0069cf0642457f87a01a014f6dc29d5d893cd4fd8fddf0c3cdfad1bb3ebafc41"
+dependencies = [
+ "alloy-primitives",
+ "alloy-rlp",
+ "serde",
+]
+
+[[package]]
+name = "alloy-eip7702"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cabf647eb4650c91a9d38cb6f972bb320009e7e9d61765fb688a86f1563b33e8"
+dependencies = [
+ "alloy-primitives",
+ "alloy-rlp",
+ "derive_more",
+ "k256",
+ "serde",
+]
+
+[[package]]
+name = "alloy-eips"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "52dd5869ed09e399003e0e0ec6903d981b2a92e74c5d37e6b40890bad2517526"
+dependencies = [
+ "alloy-eip2930",
+ "alloy-eip7702",
+ "alloy-primitives",
+ "alloy-rlp",
+ "alloy-serde",
+ "c-kzg",
+ "derive_more",
+ "once_cell",
+ "serde",
+ "sha2",
+]
+
+[[package]]
+name = "alloy-genesis"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7d2a7fe5c1a9bd6793829ea21a636f30fc2b3f5d2e7418ba86d96e41dd1f460"
+dependencies = [
+ "alloy-eips",
+ "alloy-primitives",
+ "alloy-serde",
+ "alloy-trie",
+ "serde",
+]
+
+[[package]]
+name = "alloy-json-abi"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24acd2f5ba97c7a320e67217274bc81fe3c3174b8e6144ec875d9d54e760e278"
+dependencies = [
+ "alloy-primitives",
+ "alloy-sol-type-parser",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "alloy-json-rpc"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2008bedb8159a255b46b7c8614516eda06679ea82f620913679afbd8031fea72"
+dependencies = [
+ "alloy-primitives",
+ "alloy-sol-types",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.11",
+ "tracing",
+]
+
+[[package]]
+name = "alloy-network"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4556f01fe41d0677495df10a648ddcf7ce118b0e8aa9642a0e2b6dd1fb7259de"
+dependencies = [
+ "alloy-consensus",
+ "alloy-consensus-any",
+ "alloy-eips",
+ "alloy-json-rpc",
+ "alloy-network-primitives",
+ "alloy-primitives",
+ "alloy-rpc-types-any",
+ "alloy-rpc-types-eth",
+ "alloy-serde",
+ "alloy-signer",
+ "alloy-sol-types",
+ "async-trait",
+ "auto_impl",
+ "futures-utils-wasm",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.11",
+]
+
+[[package]]
+name = "alloy-network-primitives"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31c3c6b71340a1d076831823f09cb6e02de01de5c6630a9631bdb36f947ff80"
+dependencies = [
+ "alloy-consensus",
+ "alloy-eips",
+ "alloy-primitives",
+ "alloy-serde",
+ "serde",
+]
+
+[[package]]
+name = "alloy-primitives"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec878088ec6283ce1e90d280316aadd3d6ce3de06ff63d68953c855e7e447e92"
+dependencies = [
+ "alloy-rlp",
+ "bytes",
+ "cfg-if",
+ "const-hex",
+ "derive_more",
+ "foldhash",
+ "hashbrown 0.15.2",
+ "indexmap",
+ "itoa",
+ "k256",
+ "keccak-asm",
+ "paste",
+ "proptest",
+ "rand",
+ "ruint",
+ "rustc-hash",
+ "serde",
+ "sha3",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "alloy-provider"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a22c4441b3ebe2d77fa9cf629ba68c3f713eb91779cff84275393db97eddd82"
+dependencies = [
+ "alloy-chains",
+ "alloy-consensus",
+ "alloy-eips",
+ "alloy-json-rpc",
+ "alloy-network",
+ "alloy-network-primitives",
+ "alloy-primitives",
+ "alloy-pubsub",
+ "alloy-rpc-client",
+ "alloy-rpc-types-eth",
+ "alloy-transport",
+ "alloy-transport-http",
+ "alloy-transport-ipc",
+ "alloy-transport-ws",
+ "async-stream",
+ "async-trait",
+ "auto_impl",
+ "dashmap",
+ "futures",
+ "futures-utils-wasm",
+ "lru",
+ "parking_lot",
+ "pin-project",
+ "reqwest",
+ "schnellru",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.11",
+ "tokio",
+ "tracing",
+ "url",
+ "wasmtimer",
+]
+
+[[package]]
+name = "alloy-pubsub"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2269fd635f7b505f27c63a3cb293148cd02301efce4c8bdd9ff54fbfc4a20e23"
+dependencies = [
+ "alloy-json-rpc",
+ "alloy-primitives",
+ "alloy-transport",
+ "bimap",
+ "futures",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tokio-stream",
+ "tower",
+ "tracing",
+]
+
+[[package]]
+name = "alloy-rlp"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6"
+dependencies = [
+ "alloy-rlp-derive",
+ "arrayvec",
+ "bytes",
+]
+
+[[package]]
+name = "alloy-rlp-derive"
+version = "0.3.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "alloy-rpc-client"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d06a292b37e182e514903ede6e623b9de96420e8109ce300da288a96d88b7e4b"
+dependencies = [
+ "alloy-json-rpc",
+ "alloy-primitives",
+ "alloy-pubsub",
+ "alloy-transport",
+ "alloy-transport-http",
+ "alloy-transport-ipc",
+ "alloy-transport-ws",
+ "futures",
+ "pin-project",
+ "reqwest",
+ "serde",
+ "serde_json",
+ "tokio",
+ "tokio-stream",
+ "tower",
+ "tracing",
+ "url",
+ "wasmtimer",
+]
+
+[[package]]
+name = "alloy-rpc-types"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9383845dd924939e7ab0298bbfe231505e20928907d7905aa3bf112287305e06"
+dependencies = [
+ "alloy-primitives",
+ "alloy-rpc-types-engine",
+ "alloy-rpc-types-eth",
+ "alloy-serde",
+ "serde",
+]
+
+[[package]]
+name = "alloy-rpc-types-any"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca445cef0eb6c2cf51cfb4e214fbf1ebd00893ae2e6f3b944c8101b07990f988"
+dependencies = [
+ "alloy-consensus-any",
+ "alloy-rpc-types-eth",
+ "alloy-serde",
+]
+
+[[package]]
+name = "alloy-rpc-types-engine"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f821f30344862a0b6eb9a1c2eb91dfb2ff44c7489f37152a526cdcab79264"
+dependencies = [
+ "alloy-consensus",
+ "alloy-eips",
+ "alloy-primitives",
+ "alloy-rlp",
+ "alloy-serde",
+ "derive_more",
+ "serde",
+ "strum",
+]
+
+[[package]]
+name = "alloy-rpc-types-eth"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0938bc615c02421bd86c1733ca7205cc3d99a122d9f9bff05726bd604b76a5c2"
+dependencies = [
+ "alloy-consensus",
+ "alloy-consensus-any",
+ "alloy-eips",
+ "alloy-network-primitives",
+ "alloy-primitives",
+ "alloy-rlp",
+ "alloy-serde",
+ "alloy-sol-types",
+ "itertools 0.13.0",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.11",
+]
+
+[[package]]
+name = "alloy-serde"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ae0465c71d4dced7525f408d84873aeebb71faf807d22d74c4a426430ccd9b55"
+dependencies = [
+ "alloy-primitives",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "alloy-signer"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bfa395ad5cc952c82358d31e4c68b27bf4a89a5456d9b27e226e77dac50e4ff"
+dependencies = [
+ "alloy-primitives",
+ "async-trait",
+ "auto_impl",
+ "elliptic-curve",
+ "k256",
+ "thiserror 2.0.11",
+]
+
+[[package]]
+name = "alloy-signer-local"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fbdc63ce9eda1283fcbaca66ba4a414b841c0e3edbeef9c86a71242fc9e84ccc"
+dependencies = [
+ "alloy-consensus",
+ "alloy-network",
+ "alloy-primitives",
+ "alloy-signer",
+ "async-trait",
+ "k256",
+ "rand",
+ "thiserror 2.0.11",
+]
+
+[[package]]
+name = "alloy-sol-macro"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d039d267aa5cbb7732fa6ce1fd9b5e9e29368f580f80ba9d7a8450c794de4b2"
+dependencies = [
+ "alloy-sol-macro-expander",
+ "alloy-sol-macro-input",
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "alloy-sol-macro-expander"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "620ae5eee30ee7216a38027dec34e0585c55099f827f92f50d11e3d2d3a4a954"
+dependencies = [
+ "alloy-json-abi",
+ "alloy-sol-macro-input",
+ "const-hex",
+ "heck",
+ "indexmap",
+ "proc-macro-error2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+ "syn-solidity",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "alloy-sol-macro-input"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad9f7d057e00f8c5994e4ff4492b76532c51ead39353aa2ed63f8c50c0f4d52e"
+dependencies = [
+ "alloy-json-abi",
+ "const-hex",
+ "dunce",
+ "heck",
+ "proc-macro2",
+ "quote",
+ "serde_json",
+ "syn 2.0.96",
+ "syn-solidity",
+]
+
+[[package]]
+name = "alloy-sol-type-parser"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74e60b084fe1aef8acecda2743ff2d93c18ff3eb67a2d3b12f62582a1e66ef5e"
+dependencies = [
+ "serde",
+ "winnow",
+]
+
+[[package]]
+name = "alloy-sol-types"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1382302752cd751efd275f4d6ef65877ddf61e0e6f5ac84ef4302b79a33a31a"
+dependencies = [
+ "alloy-json-abi",
+ "alloy-primitives",
+ "alloy-sol-macro",
+ "const-hex",
+ "serde",
+]
+
+[[package]]
+name = "alloy-transport"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d17722a198f33bbd25337660787aea8b8f57814febb7c746bc30407bdfc39448"
+dependencies = [
+ "alloy-json-rpc",
+ "base64",
+ "futures-util",
+ "futures-utils-wasm",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.11",
+ "tokio",
+ "tower",
+ "tracing",
+ "url",
+ "wasmtimer",
+]
+
+[[package]]
+name = "alloy-transport-http"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e1509599021330a31c4a6816b655e34bf67acb1cc03c564e09fd8754ff6c5de"
+dependencies = [
+ "alloy-json-rpc",
+ "alloy-transport",
+ "reqwest",
+ "serde_json",
+ "tower",
+ "tracing",
+ "url",
+]
+
+[[package]]
+name = "alloy-transport-ipc"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4da44bc9a5155ab599666d26decafcf12204b72a80eeaba7c5e234ee8ac205"
+dependencies = [
+ "alloy-json-rpc",
+ "alloy-pubsub",
+ "alloy-transport",
+ "bytes",
+ "futures",
+ "interprocess",
+ "pin-project",
+ "serde_json",
+ "tokio",
+ "tokio-util",
+ "tracing",
+]
+
+[[package]]
+name = "alloy-transport-ws"
+version = "0.9.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58011745b2f17b334db40df9077d75b181f78360a5bc5c35519e15d4bfce15e2"
+dependencies = [
+ "alloy-pubsub",
+ "alloy-transport",
+ "futures",
+ "http",
+ "rustls",
+ "serde_json",
+ "tokio",
+ "tokio-tungstenite",
+ "tracing",
+ "ws_stream_wasm",
+]
+
+[[package]]
+name = "alloy-trie"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6917c79e837aa7b77b7a6dae9f89cbe15313ac161c4d3cfaf8909ef21f3d22d8"
+dependencies = [
+ "alloy-primitives",
+ "alloy-rlp",
+ "arrayvec",
+ "derive_more",
+ "nybbles",
+ "serde",
+ "smallvec",
+ "tracing",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "anstream"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
+dependencies = [
+ "anstyle",
+ "once_cell",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "anyhow"
+version = "1.0.95"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
+
+[[package]]
+name = "ark-ff"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6"
+dependencies = [
+ "ark-ff-asm 0.3.0",
+ "ark-ff-macros 0.3.0",
+ "ark-serialize 0.3.0",
+ "ark-std 0.3.0",
+ "derivative",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rustc_version 0.3.3",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba"
+dependencies = [
+ "ark-ff-asm 0.4.2",
+ "ark-ff-macros 0.4.2",
+ "ark-serialize 0.4.2",
+ "ark-std 0.4.0",
+ "derivative",
+ "digest 0.10.7",
+ "itertools 0.10.5",
+ "num-bigint",
+ "num-traits",
+ "paste",
+ "rustc_version 0.4.1",
+ "zeroize",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-asm"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348"
+dependencies = [
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-ff-macros"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565"
+dependencies = [
+ "num-bigint",
+ "num-traits",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671"
+dependencies = [
+ "ark-std 0.3.0",
+ "digest 0.9.0",
+]
+
+[[package]]
+name = "ark-serialize"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5"
+dependencies = [
+ "ark-std 0.4.0",
+ "digest 0.10.7",
+ "num-bigint",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
+[[package]]
+name = "ark-std"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185"
+dependencies = [
+ "num-traits",
+ "rand",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "arrow"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2ccdcc8fb14508ca20aaec7076032e5c0b0751b906036d4496786e2f227a37a"
+dependencies = [
+ "arrow-arith",
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-cast",
+ "arrow-csv",
+ "arrow-data",
+ "arrow-ipc",
+ "arrow-json",
+ "arrow-ord",
+ "arrow-row",
+ "arrow-schema",
+ "arrow-select",
+ "arrow-string",
+]
+
+[[package]]
+name = "arrow-arith"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1aad8e27f32e411a0fc0bf5a625a35f0bf9b9f871cf4542abe31f7cef4beea2"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "chrono",
+ "num",
+]
+
+[[package]]
+name = "arrow-array"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd6ed90c28c6f73a706c55799b8cc3a094e89257238e5b1d65ca7c70bd3ae23f"
+dependencies = [
+ "ahash",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "chrono",
+ "half",
+ "hashbrown 0.15.2",
+ "num",
+]
+
+[[package]]
+name = "arrow-buffer"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe4a40bdc1552ea10fbdeae4e5a945d8572c32f66bce457b96c13d9c46b80447"
+dependencies = [
+ "bytes",
+ "half",
+ "num",
+]
+
+[[package]]
+name = "arrow-cast"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "430c0a21aa7f81bcf0f97c57216d7127795ea755f494d27bae2bd233be43c2cc"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "arrow-select",
+ "atoi",
+ "base64",
+ "chrono",
+ "half",
+ "lexical-core",
+ "num",
+ "ryu",
+]
+
+[[package]]
+name = "arrow-csv"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b4444c8f8c57ac00e6a679ede67d1ae8872c170797dc45b46f75702437a77888"
+dependencies = [
+ "arrow-array",
+ "arrow-cast",
+ "arrow-schema",
+ "chrono",
+ "csv",
+ "csv-core",
+ "lazy_static",
+ "regex",
+]
+
+[[package]]
+name = "arrow-data"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09af476cfbe9879937e50b1334c73189de6039186e025b1b1ac84b283b87b20e"
+dependencies = [
+ "arrow-buffer",
+ "arrow-schema",
+ "half",
+ "num",
+]
+
+[[package]]
+name = "arrow-ipc"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "136296e8824333a8a4c4a6e508e4aa65d5678b801246d0408825ae7b2523c628"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "flatbuffers",
+]
+
+[[package]]
+name = "arrow-json"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e222ad0e419ab8276818c5605a5bb1e35ed86fa8c5e550726433cc63b09c3c78"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-cast",
+ "arrow-data",
+ "arrow-schema",
+ "chrono",
+ "half",
+ "indexmap",
+ "lexical-core",
+ "num",
+ "serde",
+ "serde_json",
+]
+
+[[package]]
+name = "arrow-ord"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eddf14c5f03b679ec8ceac4dfac43f63cdc4ed54dab3cc120a4ef46af38481eb"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "arrow-select",
+]
+
+[[package]]
+name = "arrow-row"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9acdc58da19f383f4ba381fa0e3583534ae2ceb31269aaf4a03f08ff13e8443"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "half",
+]
+
+[[package]]
+name = "arrow-schema"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3a1822a1a952955637e85e8f9d6b0e04dd75d65492b87ec548dd593d3a1f772b"
+
+[[package]]
+name = "arrow-select"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c4172e9a12dfe15303d3926269f9ead471ea93bdd067d113abc65cb6c48e246"
+dependencies = [
+ "ahash",
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "num",
+]
+
+[[package]]
+name = "arrow-string"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73683040445f4932342781926189901c9521bb1a787c35dbe628a3ce51372d3c"
+dependencies = [
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-data",
+ "arrow-schema",
+ "arrow-select",
+ "memchr",
+ "num",
+ "regex",
+ "regex-syntax",
+]
+
+[[package]]
+name = "async-stream"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
+dependencies = [
+ "async-stream-impl",
+ "futures-core",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "async-stream-impl"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "async-trait"
+version = "0.1.85"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "async_io_stream"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6d7b9decdf35d8908a7e3ef02f64c5e9b1695e230154c0e8de3969142d9b94c"
+dependencies = [
+ "futures",
+ "pharos",
+ "rustc_version 0.4.1",
+]
+
+[[package]]
+name = "atoi"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "auto_impl"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e12882f59de5360c748c4cbf569a042d5fb0eb515f7bea9c1f470b47f6ffbd73"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "backtrace"
+version = "0.3.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
+dependencies = [
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+ "windows-targets",
+]
+
+[[package]]
+name = "base16ct"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
+[[package]]
+name = "base64ct"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
+
+[[package]]
+name = "bimap"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7"
+
+[[package]]
+name = "bit-set"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3"
+dependencies = [
+ "bit-vec",
+]
+
+[[package]]
+name = "bit-vec"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "bitflags"
+version = "2.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
+
+[[package]]
+name = "bitvec"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
+dependencies = [
+ "funty",
+ "radium",
+ "tap",
+ "wyz",
+]
+
+[[package]]
+name = "block-buffer"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "block_extractor"
+version = "0.1.0"
+dependencies = [
+ "alloy",
+ "anyhow",
+ "arrow",
+ "csv",
+ "dotenv",
+ "env_logger",
+ "futures",
+ "indicatif",
+ "log",
+ "parquet",
+ "tokio",
+]
+
+[[package]]
+name = "blst"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4378725facc195f1a538864863f6de233b500a8862747e7f165078a419d5e874"
+dependencies = [
+ "cc",
+ "glob",
+ "threadpool",
+ "zeroize",
+]
+
+[[package]]
+name = "brotli"
+version = "7.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+ "brotli-decompressor",
+]
+
+[[package]]
+name = "brotli-decompressor"
+version = "4.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "74fa05ad7d803d413eb8380983b092cbbaf9a85f151b871360e7b00cd7060b37"
+dependencies = [
+ "alloc-no-stdlib",
+ "alloc-stdlib",
+]
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "byte-slice-cast"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "bytes"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "c-kzg"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0307f72feab3300336fb803a57134159f6e20139af1357f36c54cb90d8e8928"
+dependencies = [
+ "blst",
+ "cc",
+ "glob",
+ "hex",
+ "libc",
+ "once_cell",
+ "serde",
+]
+
+[[package]]
+name = "cc"
+version = "1.2.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
+dependencies = [
+ "jobserver",
+ "libc",
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "num-traits",
+ "windows-targets",
+]
+
+[[package]]
+name = "colorchoice"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
+
+[[package]]
+name = "console"
+version = "0.15.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea3c6ecd8059b57859df5c69830340ed3c41d30e3da0c1cbed90a96ac853041b"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "once_cell",
+ "unicode-width",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "const-hex"
+version = "1.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4b0485bab839b018a8f1723fc5391819fea5f8f0f32288ef8a735fd096b6160c"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "hex",
+ "proptest",
+ "serde",
+]
+
+[[package]]
+name = "const-oid"
+version = "0.9.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8"
+
+[[package]]
+name = "const-random"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359"
+dependencies = [
+ "const-random-macro",
+]
+
+[[package]]
+name = "const-random-macro"
+version = "0.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "tiny-keccak",
+]
+
+[[package]]
+name = "core-foundation"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "cpufeatures"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "crc32fast"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
+
+[[package]]
+name = "crypto-bigint"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
+dependencies = [
+ "generic-array",
+ "rand_core",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto-common"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
+dependencies = [
+ "generic-array",
+ "typenum",
+]
+
+[[package]]
+name = "csv"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf"
+dependencies = [
+ "csv-core",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "csv-core"
+version = "0.1.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "dashmap"
+version = "6.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+ "hashbrown 0.14.5",
+ "lock_api",
+ "once_cell",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "data-encoding"
+version = "2.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e60eed09d8c01d3cee5b7d30acb059b76614c918fa0f992e0dd6eeb10daad6f"
+
+[[package]]
+name = "der"
+version = "0.7.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0"
+dependencies = [
+ "const-oid",
+ "zeroize",
+]
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "derive_more"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05"
+dependencies = [
+ "derive_more-impl",
+]
+
+[[package]]
+name = "derive_more-impl"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+ "unicode-xid",
+]
+
+[[package]]
+name = "digest"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
+name = "digest"
+version = "0.10.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
+dependencies = [
+ "block-buffer",
+ "const-oid",
+ "crypto-common",
+ "subtle",
+]
+
+[[package]]
+name = "displaydoc"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "doctest-file"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aac81fa3e28d21450aa4d2ac065992ba96a1d7303efbce51a95f4fd175b67562"
+
+[[package]]
+name = "dotenv"
+version = "0.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
+
+[[package]]
+name = "dunce"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
+
+[[package]]
+name = "ecdsa"
+version = "0.16.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca"
+dependencies = [
+ "der",
+ "digest 0.10.7",
+ "elliptic-curve",
+ "rfc6979",
+ "signature",
+ "spki",
+]
+
+[[package]]
+name = "either"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
+
+[[package]]
+name = "elliptic-curve"
+version = "0.13.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
+dependencies = [
+ "base16ct",
+ "crypto-bigint",
+ "digest 0.10.7",
+ "ff",
+ "generic-array",
+ "group",
+ "pkcs8",
+ "rand_core",
+ "sec1",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "encode_unicode"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
+
+[[package]]
+name = "env_filter"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
+dependencies = [
+ "log",
+ "regex",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.11.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "env_filter",
+ "humantime",
+ "log",
+]
+
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
+[[package]]
+name = "errno"
+version = "0.3.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "fastrand"
+version = "2.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
+
+[[package]]
+name = "fastrlp"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418"
+dependencies = [
+ "arrayvec",
+ "auto_impl",
+ "bytes",
+]
+
+[[package]]
+name = "ff"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
+dependencies = [
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "fixed-hash"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534"
+dependencies = [
+ "byteorder",
+ "rand",
+ "rustc-hex",
+ "static_assertions",
+]
+
+[[package]]
+name = "flatbuffers"
+version = "24.12.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4f1baf0dbf96932ec9a3038d57900329c015b0bfb7b63d904f3bc27e2b02a096"
+dependencies = [
+ "bitflags 1.3.2",
+ "rustc_version 0.4.1",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "foldhash"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "form_urlencoded"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
+dependencies = [
+ "percent-encoding",
+]
+
+[[package]]
+name = "funty"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
+
+[[package]]
+name = "futures"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-channel"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
+dependencies = [
+ "futures-core",
+ "futures-sink",
+]
+
+[[package]]
+name = "futures-core"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
+
+[[package]]
+name = "futures-executor"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
+name = "futures-io"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
+
+[[package]]
+name = "futures-macro"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "futures-sink"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
+
+[[package]]
+name = "futures-task"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
+
+[[package]]
+name = "futures-util"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
+dependencies = [
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project-lite",
+ "pin-utils",
+ "slab",
+]
+
+[[package]]
+name = "futures-utils-wasm"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9"
+
+[[package]]
+name = "generic-array"
+version = "0.14.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
+dependencies = [
+ "typenum",
+ "version_check",
+ "zeroize",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gimli"
+version = "0.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
+
+[[package]]
+name = "glob"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
+
+[[package]]
+name = "group"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
+dependencies = [
+ "ff",
+ "rand_core",
+ "subtle",
+]
+
+[[package]]
+name = "half"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+ "num-traits",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+
+[[package]]
+name = "hashbrown"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
+dependencies = [
+ "allocator-api2",
+ "equivalent",
+ "foldhash",
+ "serde",
+]
+
+[[package]]
+name = "heck"
+version = "0.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
+
+[[package]]
+name = "hermit-abi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
+
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "hmac"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
+dependencies = [
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "http"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-body"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
+dependencies = [
+ "bytes",
+ "http",
+]
+
+[[package]]
+name = "http-body-util"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
+dependencies = [
+ "bytes",
+ "futures-util",
+ "http",
+ "http-body",
+ "pin-project-lite",
+]
+
+[[package]]
+name = "httparse"
+version = "1.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946"
+
+[[package]]
+name = "humantime"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+
+[[package]]
+name = "hyper"
+version = "1.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "httparse",
+ "itoa",
+ "pin-project-lite",
+ "smallvec",
+ "tokio",
+ "want",
+]
+
+[[package]]
+name = "hyper-tls"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
+dependencies = [
+ "bytes",
+ "http-body-util",
+ "hyper",
+ "hyper-util",
+ "native-tls",
+ "tokio",
+ "tokio-native-tls",
+ "tower-service",
+]
+
+[[package]]
+name = "hyper-util"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
+dependencies = [
+ "bytes",
+ "futures-channel",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "pin-project-lite",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+]
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.61"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "icu_collections"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526"
+dependencies = [
+ "displaydoc",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637"
+dependencies = [
+ "displaydoc",
+ "litemap",
+ "tinystr",
+ "writeable",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_locid_transform_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_locid_transform_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e"
+
+[[package]]
+name = "icu_normalizer"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_normalizer_data",
+ "icu_properties",
+ "icu_provider",
+ "smallvec",
+ "utf16_iter",
+ "utf8_iter",
+ "write16",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_normalizer_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516"
+
+[[package]]
+name = "icu_properties"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5"
+dependencies = [
+ "displaydoc",
+ "icu_collections",
+ "icu_locid_transform",
+ "icu_properties_data",
+ "icu_provider",
+ "tinystr",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_properties_data"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569"
+
+[[package]]
+name = "icu_provider"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9"
+dependencies = [
+ "displaydoc",
+ "icu_locid",
+ "icu_provider_macros",
+ "stable_deref_trait",
+ "tinystr",
+ "writeable",
+ "yoke",
+ "zerofrom",
+ "zerovec",
+]
+
+[[package]]
+name = "icu_provider_macros"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "idna"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e"
+dependencies = [
+ "idna_adapter",
+ "smallvec",
+ "utf8_iter",
+]
+
+[[package]]
+name = "idna_adapter"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71"
+dependencies = [
+ "icu_normalizer",
+ "icu_properties",
+]
+
+[[package]]
+name = "impl-codec"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f"
+dependencies = [
+ "parity-scale-codec",
+]
+
+[[package]]
+name = "impl-trait-for-tuples"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "indexmap"
+version = "2.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
+dependencies = [
+ "equivalent",
+ "hashbrown 0.15.2",
+ "serde",
+]
+
+[[package]]
+name = "indicatif"
+version = "0.17.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf675b85ed934d3c67b5c5469701eec7db22689d0a2139d856e0925fa28b281"
+dependencies = [
+ "console",
+ "number_prefix",
+ "portable-atomic",
+ "unicode-width",
+ "web-time",
+]
+
+[[package]]
+name = "integer-encoding"
+version = "3.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8bb03732005da905c88227371639bf1ad885cc712789c011c31c5fb3ab3ccf02"
+
+[[package]]
+name = "interprocess"
+version = "2.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "894148491d817cb36b6f778017b8ac46b17408d522dd90f539d677ea938362eb"
+dependencies = [
+ "doctest-file",
+ "futures-core",
+ "libc",
+ "recvmsg",
+ "tokio",
+ "widestring",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "ipnet"
+version = "2.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
+
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
+
+[[package]]
+name = "jobserver"
+version = "0.1.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "k256"
+version = "0.13.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b"
+dependencies = [
+ "cfg-if",
+ "ecdsa",
+ "elliptic-curve",
+ "once_cell",
+ "sha2",
+]
+
+[[package]]
+name = "keccak"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654"
+dependencies = [
+ "cpufeatures",
+]
+
+[[package]]
+name = "keccak-asm"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6"
+dependencies = [
+ "digest 0.10.7",
+ "sha3-asm",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
+[[package]]
+name = "lexical-core"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b765c31809609075565a70b4b71402281283aeda7ecaf4818ac14a7b2ade8958"
+dependencies = [
+ "lexical-parse-float",
+ "lexical-parse-integer",
+ "lexical-util",
+ "lexical-write-float",
+ "lexical-write-integer",
+]
+
+[[package]]
+name = "lexical-parse-float"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de6f9cb01fb0b08060209a057c048fcbab8717b4c1ecd2eac66ebfe39a65b0f2"
+dependencies = [
+ "lexical-parse-integer",
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-parse-integer"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72207aae22fc0a121ba7b6d479e42cbfea549af1479c3f3a4f12c70dd66df12e"
+dependencies = [
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-util"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a82e24bf537fd24c177ffbbdc6ebcc8d54732c35b50a3f28cc3f4e4c949a0b3"
+dependencies = [
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-write-float"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c5afc668a27f460fb45a81a757b6bf2f43c2d7e30cb5a2dcd3abf294c78d62bd"
+dependencies = [
+ "lexical-util",
+ "lexical-write-integer",
+ "static_assertions",
+]
+
+[[package]]
+name = "lexical-write-integer"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "629ddff1a914a836fb245616a7888b62903aae58fa771e1d83943035efa0f978"
+dependencies = [
+ "lexical-util",
+ "static_assertions",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.169"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
+
+[[package]]
+name = "libm"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
+
+[[package]]
+name = "linux-raw-sys"
+version = "0.4.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
+
+[[package]]
+name = "litemap"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104"
+
+[[package]]
+name = "lock_api"
+version = "0.4.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
+
+[[package]]
+name = "lru"
+version = "0.12.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
+dependencies = [
+ "hashbrown 0.15.2",
+]
+
+[[package]]
+name = "lz4_flex"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5"
+dependencies = [
+ "twox-hash",
+]
+
+[[package]]
+name = "memchr"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "mime"
+version = "0.3.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
+
+[[package]]
+name = "miniz_oxide"
+version = "0.8.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
+dependencies = [
+ "adler2",
+]
+
+[[package]]
+name = "mio"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
+dependencies = [
+ "libc",
+ "wasi",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "native-tls"
+version = "0.2.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
+dependencies = [
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
+]
+
+[[package]]
+name = "num"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23"
+dependencies = [
+ "num-bigint",
+ "num-complex",
+ "num-integer",
+ "num-iter",
+ "num-rational",
+ "num-traits",
+]
+
+[[package]]
+name = "num-bigint"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9"
+dependencies = [
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-complex"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.46"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-iter"
+version = "0.1.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824"
+dependencies = [
+ "num-bigint",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+ "libm",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "num_enum"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "number_prefix"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
+
+[[package]]
+name = "nybbles"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8983bb634df7248924ee0c4c3a749609b5abcb082c28fffe3254b3eb3602b307"
+dependencies = [
+ "alloy-rlp",
+ "const-hex",
+ "proptest",
+ "serde",
+ "smallvec",
+]
+
+[[package]]
+name = "object"
+version = "0.36.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.20.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
+
+[[package]]
+name = "openssl"
+version = "0.10.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6"
+dependencies = [
+ "bitflags 2.8.0",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "openssl-probe"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.105"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc"
+dependencies = [
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "ordered-float"
+version = "2.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f19d67e5a2795c94e73e0bb1cc1a7edeb2e28efd39e2e1c9b7a40c1108b11c"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "parity-scale-codec"
+version = "3.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee"
+dependencies = [
+ "arrayvec",
+ "bitvec",
+ "byte-slice-cast",
+ "impl-trait-for-tuples",
+ "parity-scale-codec-derive",
+ "serde",
+]
+
+[[package]]
+name = "parity-scale-codec-derive"
+version = "3.6.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 1.0.109",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
+dependencies = [
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.9.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "windows-targets",
+]
+
+[[package]]
+name = "parquet"
+version = "54.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3334c50239d9f4951653d84fa6f636da86f53742e5e5849a30fbe852f3ff4383"
+dependencies = [
+ "ahash",
+ "arrow-array",
+ "arrow-buffer",
+ "arrow-cast",
+ "arrow-data",
+ "arrow-ipc",
+ "arrow-schema",
+ "arrow-select",
+ "base64",
+ "brotli",
+ "bytes",
+ "chrono",
+ "flate2",
+ "half",
+ "hashbrown 0.15.2",
+ "lz4_flex",
+ "num",
+ "num-bigint",
+ "paste",
+ "seq-macro",
+ "snap",
+ "thrift",
+ "twox-hash",
+ "zstd",
+ "zstd-sys",
+]
+
+[[package]]
+name = "paste"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
+
+[[package]]
+name = "percent-encoding"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
+
+[[package]]
+name = "pest"
+version = "2.7.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b7cafe60d6cf8e62e1b9b2ea516a089c008945bb5a275416789e7db0bc199dc"
+dependencies = [
+ "memchr",
+ "thiserror 2.0.11",
+ "ucd-trie",
+]
+
+[[package]]
+name = "pharos"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9567389417feee6ce15dd6527a8a1ecac205ef62c2932bcf3d9f6fc5b78b414"
+dependencies = [
+ "futures",
+ "rustc_version 0.4.1",
+]
+
+[[package]]
+name = "pin-project"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e2ec53ad785f4d35dac0adea7f7dc6f1bb277ad84a680c7afefeae05d1f5916"
+dependencies = [
+ "pin-project-internal",
+]
+
+[[package]]
+name = "pin-project-internal"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d56a66c0c55993aa927429d0f8a0abfd74f084e4d9c192cffed01e418d83eefb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
+
+[[package]]
+name = "pin-utils"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
+
+[[package]]
+name = "pkcs8"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7"
+dependencies = [
+ "der",
+ "spki",
+]
+
+[[package]]
+name = "pkg-config"
+version = "0.3.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
+
+[[package]]
+name = "portable-atomic"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
+dependencies = [
+ "zerocopy",
+]
+
+[[package]]
+name = "primitive-types"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2"
+dependencies = [
+ "fixed-hash",
+ "impl-codec",
+ "uint",
+]
+
+[[package]]
+name = "proc-macro-crate"
+version = "3.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b"
+dependencies = [
+ "toml_edit",
+]
+
+[[package]]
+name = "proc-macro-error-attr2"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro-error2"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802"
+dependencies = [
+ "proc-macro-error-attr2",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "proptest"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50"
+dependencies = [
+ "bit-set",
+ "bit-vec",
+ "bitflags 2.8.0",
+ "lazy_static",
+ "num-traits",
+ "rand",
+ "rand_chacha",
+ "rand_xorshift",
+ "regex-syntax",
+ "rusty-fork",
+ "tempfile",
+ "unarray",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
+
+[[package]]
+name = "quote"
+version = "1.0.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "radium"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "serde",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_xorshift"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "recvmsg"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3edd4d5d42c92f0a659926464d4cce56b562761267ecf0f469d85b7de384175"
+
+[[package]]
+name = "redox_syscall"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
+dependencies = [
+ "bitflags 2.8.0",
+]
+
+[[package]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "reqwest"
+version = "0.12.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43e734407157c3c2034e0258f5e4473ddb361b1e85f95a66690d67264d7cd1da"
+dependencies = [
+ "base64",
+ "bytes",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "http-body-util",
+ "hyper",
+ "hyper-tls",
+ "hyper-util",
+ "ipnet",
+ "js-sys",
+ "log",
+ "mime",
+ "native-tls",
+ "once_cell",
+ "percent-encoding",
+ "pin-project-lite",
+ "rustls-pemfile",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "sync_wrapper",
+ "tokio",
+ "tokio-native-tls",
+ "tower",
+ "tower-service",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "windows-registry",
+]
+
+[[package]]
+name = "rfc6979"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
+dependencies = [
+ "hmac",
+ "subtle",
+]
+
+[[package]]
+name = "ring"
+version = "0.17.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
+dependencies = [
+ "cc",
+ "cfg-if",
+ "getrandom",
+ "libc",
+ "spin",
+ "untrusted",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "rlp"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec"
+dependencies = [
+ "bytes",
+ "rustc-hex",
+]
+
+[[package]]
+name = "ruint"
+version = "1.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c3cc4c2511671f327125da14133d0c5c5d137f006a1017a16f557bc85b16286"
+dependencies = [
+ "alloy-rlp",
+ "ark-ff 0.3.0",
+ "ark-ff 0.4.2",
+ "bytes",
+ "fastrlp",
+ "num-bigint",
+ "num-traits",
+ "parity-scale-codec",
+ "primitive-types",
+ "proptest",
+ "rand",
+ "rlp",
+ "ruint-macro",
+ "serde",
+ "valuable",
+ "zeroize",
+]
+
+[[package]]
+name = "ruint-macro"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18"
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+
+[[package]]
+name = "rustc-hash"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497"
+
+[[package]]
+name = "rustc-hex"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6"
+
+[[package]]
+name = "rustc_version"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
+dependencies = [
+ "semver 0.11.0",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92"
+dependencies = [
+ "semver 1.0.25",
+]
+
+[[package]]
+name = "rustix"
+version = "0.38.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
+dependencies = [
+ "bitflags 2.8.0",
+ "errno",
+ "libc",
+ "linux-raw-sys",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "rustls"
+version = "0.23.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f287924602bf649d949c63dc8ac8b235fa5387d394020705b80c4eb597ce5b8"
+dependencies = [
+ "once_cell",
+ "ring",
+ "rustls-pki-types",
+ "rustls-webpki",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "rustls-pemfile"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "rustls-pki-types"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37"
+
+[[package]]
+name = "rustls-webpki"
+version = "0.102.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9"
+dependencies = [
+ "ring",
+ "rustls-pki-types",
+ "untrusted",
+]
+
+[[package]]
+name = "rustversion"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
+
+[[package]]
+name = "rusty-fork"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f"
+dependencies = [
+ "fnv",
+ "quick-error",
+ "tempfile",
+ "wait-timeout",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+
+[[package]]
+name = "schannel"
+version = "0.1.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "schnellru"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "356285bbf17bea63d9e52e96bd18f039672ac92b55b8cb997d6162a2a37d1649"
+dependencies = [
+ "ahash",
+ "cfg-if",
+ "hashbrown 0.13.2",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+
+[[package]]
+name = "sec1"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc"
+dependencies = [
+ "base16ct",
+ "der",
+ "generic-array",
+ "pkcs8",
+ "subtle",
+ "zeroize",
+]
+
+[[package]]
+name = "security-framework"
+version = "2.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
+dependencies = [
+ "bitflags 2.8.0",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
+]
+
+[[package]]
+name = "security-framework-sys"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
+dependencies = [
+ "core-foundation-sys",
+ "libc",
+]
+
+[[package]]
+name = "semver"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver"
+version = "1.0.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
+
+[[package]]
+name = "semver-parser"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2"
+dependencies = [
+ "pest",
+]
+
+[[package]]
+name = "send_wrapper"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
+
+[[package]]
+name = "seq-macro"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
+
+[[package]]
+name = "serde"
+version = "1.0.217"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.217"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.137"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "serde_urlencoded"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
+dependencies = [
+ "form_urlencoded",
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "sha1"
+version = "0.10.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha2"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
+dependencies = [
+ "cfg-if",
+ "cpufeatures",
+ "digest 0.10.7",
+]
+
+[[package]]
+name = "sha3"
+version = "0.10.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60"
+dependencies = [
+ "digest 0.10.7",
+ "keccak",
+]
+
+[[package]]
+name = "sha3-asm"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46"
+dependencies = [
+ "cc",
+ "cfg-if",
+]
+
+[[package]]
+name = "shlex"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
+
+[[package]]
+name = "signal-hook-registry"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "signature"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
+dependencies = [
+ "digest 0.10.7",
+ "rand_core",
+]
+
+[[package]]
+name = "slab"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "snap"
+version = "1.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b"
+
+[[package]]
+name = "socket2"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "spin"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
+
+[[package]]
+name = "spki"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d"
+dependencies = [
+ "base64ct",
+ "der",
+]
+
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
+[[package]]
+name = "static_assertions"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
+
+[[package]]
+name = "strum"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "subtle"
+version = "2.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
+
+[[package]]
+name = "syn"
+version = "1.0.109"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.96"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "syn-solidity"
+version = "0.8.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b84e4d83a0a6704561302b917a932484e1cae2d8c6354c64be8b7bac1c1fe057"
+dependencies = [
+ "paste",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "sync_wrapper"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
+dependencies = [
+ "futures-core",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "tap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
+
+[[package]]
+name = "tempfile"
+version = "3.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "getrandom",
+ "once_cell",
+ "rustix",
+ "windows-sys 0.59.0",
+]
+
+[[package]]
+name = "thiserror"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
+dependencies = [
+ "thiserror-impl 1.0.69",
+]
+
+[[package]]
+name = "thiserror"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc"
+dependencies = [
+ "thiserror-impl 2.0.11",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "1.0.69"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "thiserror-impl"
+version = "2.0.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "threadpool"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
+dependencies = [
+ "num_cpus",
+]
+
+[[package]]
+name = "thrift"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e54bc85fc7faa8bc175c4bab5b92ba8d9a3ce893d0e9f42cc455c8ab16a9e09"
+dependencies = [
+ "byteorder",
+ "integer-encoding",
+ "ordered-float",
+]
+
+[[package]]
+name = "tiny-keccak"
+version = "2.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
+dependencies = [
+ "crunchy",
+]
+
+[[package]]
+name = "tinystr"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f"
+dependencies = [
+ "displaydoc",
+ "zerovec",
+]
+
+[[package]]
+name = "tokio"
+version = "1.43.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
+dependencies = [
+ "backtrace",
+ "bytes",
+ "libc",
+ "mio",
+ "parking_lot",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "socket2",
+ "tokio-macros",
+ "windows-sys 0.52.0",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "tokio-native-tls"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
+dependencies = [
+ "native-tls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.26.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f6d0975eaace0cf0fcadee4e4aaa5da15b5c079146f2cffb67c113be122bf37"
+dependencies = [
+ "rustls",
+ "tokio",
+]
+
+[[package]]
+name = "tokio-stream"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
+dependencies = [
+ "futures-core",
+ "pin-project-lite",
+ "tokio",
+ "tokio-util",
+]
+
+[[package]]
+name = "tokio-tungstenite"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9"
+dependencies = [
+ "futures-util",
+ "log",
+ "rustls",
+ "rustls-pki-types",
+ "tokio",
+ "tokio-rustls",
+ "tungstenite",
+ "webpki-roots",
+]
+
+[[package]]
+name = "tokio-util"
+version = "0.7.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
+dependencies = [
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "pin-project-lite",
+ "tokio",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
+
+[[package]]
+name = "toml_edit"
+version = "0.22.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
+[[package]]
+name = "tower"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
+dependencies = [
+ "futures-core",
+ "futures-util",
+ "pin-project-lite",
+ "sync_wrapper",
+ "tokio",
+ "tower-layer",
+ "tower-service",
+]
+
+[[package]]
+name = "tower-layer"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
+
+[[package]]
+name = "tower-service"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
+
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "pin-project-lite",
+ "tracing-attributes",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-attributes"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "try-lock"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
+
+[[package]]
+name = "tungstenite"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a"
+dependencies = [
+ "byteorder",
+ "bytes",
+ "data-encoding",
+ "http",
+ "httparse",
+ "log",
+ "rand",
+ "rustls",
+ "rustls-pki-types",
+ "sha1",
+ "thiserror 1.0.69",
+ "utf-8",
+]
+
+[[package]]
+name = "twox-hash"
+version = "1.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675"
+dependencies = [
+ "cfg-if",
+ "static_assertions",
+]
+
+[[package]]
+name = "typenum"
+version = "1.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
+
+[[package]]
+name = "ucd-trie"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971"
+
+[[package]]
+name = "uint"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52"
+dependencies = [
+ "byteorder",
+ "crunchy",
+ "hex",
+ "static_assertions",
+]
+
+[[package]]
+name = "unarray"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11cd88e12b17c6494200a9c1b683a04fcac9573ed74cd1b62aeb2727c5592243"
+
+[[package]]
+name = "unicode-width"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
+
+[[package]]
+name = "untrusted"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
+
+[[package]]
+name = "url"
+version = "2.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60"
+dependencies = [
+ "form_urlencoded",
+ "idna",
+ "percent-encoding",
+]
+
+[[package]]
+name = "utf-8"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
+
+[[package]]
+name = "utf16_iter"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246"
+
+[[package]]
+name = "utf8_iter"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "version_check"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
+
+[[package]]
+name = "wait-timeout"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "want"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
+dependencies = [
+ "try-lock",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61"
+dependencies = [
+ "cfg-if",
+ "js-sys",
+ "once_cell",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "wasmtimer"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0048ad49a55b9deb3953841fa1fc5858f0efbcb7a18868c899a360269fac1b23"
+dependencies = [
+ "futures",
+ "js-sys",
+ "parking_lot",
+ "pin-utils",
+ "slab",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "web-time"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
+dependencies = [
+ "js-sys",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "webpki-roots"
+version = "0.26.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e"
+dependencies = [
+ "rustls-pki-types",
+]
+
+[[package]]
+name = "widestring"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-registry"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
+dependencies = [
+ "windows-result",
+ "windows-strings",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-result"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-strings"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
+dependencies = [
+ "windows-result",
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
+
+[[package]]
+name = "winnow"
+version = "0.6.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a"
+dependencies = [
+ "memchr",
+]
+
+[[package]]
+name = "write16"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936"
+
+[[package]]
+name = "writeable"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51"
+
+[[package]]
+name = "ws_stream_wasm"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7999f5f4217fe3818726b66257a4475f71e74ffd190776ad053fa159e50737f5"
+dependencies = [
+ "async_io_stream",
+ "futures",
+ "js-sys",
+ "log",
+ "pharos",
+ "rustc_version 0.4.1",
+ "send_wrapper",
+ "thiserror 1.0.69",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+]
+
+[[package]]
+name = "wyz"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
+dependencies = [
+ "tap",
+]
+
+[[package]]
+name = "yoke"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40"
+dependencies = [
+ "serde",
+ "stable_deref_trait",
+ "yoke-derive",
+ "zerofrom",
+]
+
+[[package]]
+name = "yoke-derive"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+ "synstructure",
+]
+
+[[package]]
+name = "zerocopy"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
+dependencies = [
+ "byteorder",
+ "zerocopy-derive",
+]
+
+[[package]]
+name = "zerocopy-derive"
+version = "0.7.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "zerofrom"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e"
+dependencies = [
+ "zerofrom-derive",
+]
+
+[[package]]
+name = "zerofrom-derive"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+ "synstructure",
+]
+
+[[package]]
+name = "zeroize"
+version = "1.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "zerovec"
+version = "0.10.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079"
+dependencies = [
+ "yoke",
+ "zerofrom",
+ "zerovec-derive",
+]
+
+[[package]]
+name = "zerovec-derive"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.96",
+]
+
+[[package]]
+name = "zstd"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcf2b778a664581e31e389454a7072dab1647606d44f7feea22cd5abb9c9f3f9"
+dependencies = [
+ "zstd-safe",
+]
+
+[[package]]
+name = "zstd-safe"
+version = "7.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54a3ab4db68cea366acc5c897c7b4d4d1b8994a9cd6e6f841f8964566a419059"
+dependencies = [
+ "zstd-sys",
+]
+
+[[package]]
+name = "zstd-sys"
+version = "2.0.13+zstd.1.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
diff --git a/block_extractor/Cargo.toml b/block_extractor/Cargo.toml
@@ -0,0 +1,23 @@
+[package]
+name = "block_extractor"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.95"
+alloy = { version = "0.9.2", features = ["full"] }
+tokio = { version = "1.43.0", features = ["full"] }
+csv = "1.3.1"
+indicatif = "0.17.9"
+futures = "0.3.31"
+log = "0.4.25"
+env_logger = "0.11.6"
+dotenv = "0.15.0"
+parquet = "54.0.0"
+arrow = "54.0.0"
+
+[lib]
+name = "block_extractor_rs"
+path = "src/lib.rs"
diff --git a/block_extractor/README.md b/block_extractor/README.md
@@ -0,0 +1,11 @@
+This program extracts all UniswapV2 and UniswapV3 pools and their tokens.
+Additionally it also extracts the last x blocks of price data of these pools.
+
+To run the code you will need an eth node, best your own because its not
+capped by rpc requests. Create an .env file and fill in the HTTP_URL of your
+node e.g.:
+
+~/.env
+HTTP_URL="https://eth-mainnet.public.blastapi.io"
+
+If you want logging add RUST_LOG=INFO to the .env file.
diff --git a/block_extractor/src/interfaces.rs b/block_extractor/src/interfaces.rs
@@ -0,0 +1,78 @@
+use alloy::sol;
+
+sol!(
+ #[sol(rpc)]
+ interface IUniswapV3Pool {
+ function slot0() external view returns (
+ uint160 sqrtPriceX96,
+ int24 tick,
+ uint16 observationIndex,
+ uint16 observationCardinality,
+ uint16 observationCardinalityNext,
+ uint8 feeProtocol,
+ bool unlocked
+ );
+
+ function token0() external view returns (
+ address adr
+ );
+
+ function token1() external view returns (
+ address adr
+ );
+
+ function fee() external view returns (
+ uint24 fee
+ );
+ }
+);
+
+sol!(
+ #[sol(rpc)]
+ interface IUniswapV2Pool {
+ function getReserves() external view returns (
+ uint112 reserve0,
+ uint112 reserve1,
+ uint32 blockTimestampLast
+ );
+
+ function token0() external view returns (
+ address adr
+ );
+
+ function token1() external view returns (
+ address adr
+ );
+
+ function fee() external view returns (
+ uint24 fee
+ );
+ }
+);
+
+sol!(
+ #[sol(rpc)]
+ interface IERC20 {
+ function balanceOf(address account) external view returns (
+ uint256 balance
+ );
+
+ function decimals() external view returns (
+ uint8 decimals
+ );
+
+ function name() external view returns (
+ string name
+ );
+
+ function symbol() external view returns (
+ string symbol
+ );
+ }
+);
+
+sol!(
+ #[sol(rpc)]
+ event PairCreated(address indexed token0, address indexed token1, address pair, uint);
+ event PoolCreated(address indexed token0, address indexed token1, uint24 indexed fee, int24 tickSpacing, address pool);
+);
diff --git a/block_extractor/src/lib.rs b/block_extractor/src/lib.rs
@@ -0,0 +1,9 @@
+#![allow(dead_code)]
+#![allow(unused_variables)]
+#![allow(unused_imports)]
+#![allow(unused_mut)]
+
+pub mod interfaces;
+pub mod prices;
+pub mod pools;
+pub mod tokens;
diff --git a/block_extractor/src/main.rs b/block_extractor/src/main.rs
@@ -0,0 +1,90 @@
+#![allow(dead_code)]
+#![allow(unused_variables)]
+#![allow(unused_imports)]
+
+use alloy::{
+ eips::BlockId,
+ providers::{Provider, ProviderBuilder, WsConnect}, transports::http::reqwest::Url,
+};
+use arrow::compute::filter;
+use dotenv::dotenv;
+use log::info;
+use std::path::Path;
+use anyhow::{anyhow, Result};
+use std::str::FromStr;
+use std::sync::Arc;
+
+use block_extractor_rs::{
+ interfaces::*,
+ tokens::*,
+ pools::*,
+ prices::*,
+};
+
+use std::fs::File;
+use parquet::file::reader::{FileReader, SerializedFileReader};
+
+
+#[tokio::main]
+async fn main() -> Result<()> {
+
+ dotenv().ok();
+ env_logger::init();
+
+// let rpc_url = std::env::var("WSS_URL")?;
+// let ws = WsConnect::new(rpc_url);
+// let provider = ProviderBuilder::new().on_ws(ws).await?;
+
+ let https_url = std::env::var("HTTPS_URL").unwrap();
+ let provider = ProviderBuilder::new().on_builtin(
+ https_url.as_str()
+ ).await?;
+
+// let block_number = BlockId::from(provider.get_block_number().await.unwrap());
+// let from_block_number = 10000835;
+// let chunks = 50000;
+// let (pools, pool_id) = load_pools(
+// provider.clone(),
+// Path::new("../data/pools.csv"),
+// from_block_number,
+// chunks,
+// ).await.unwrap();
+//
+// let parallel_tokens = 1;
+// let tokens = load_tokens(
+// provider.clone(),
+// Path::new("../data/tokens.csv"),
+// &pools,
+// parallel_tokens,
+// pool_id,
+// ).await.unwrap();
+//
+ let filtered_pools = load_pools_from_file(
+ Path::new("../data/pools/pools_deg_5_liq_100_block_18_grad.csv"),
+ ).unwrap();
+
+ let tokens = load_tokens_from_file(
+ Path::new("../data/tokens/tokens.csv"),
+ ).unwrap();
+
+ info!("#fltered_pools {:?}", filtered_pools.len());
+ info!("#tokens {:?}", tokens.len());
+
+ let p_to_block = provider.get_block_number().await?;
+ let p_from_block = 18000000;
+ let block_gap = 3600; // approx 12 hours
+
+ let prices = load_prices(
+ provider.clone(),
+ &filtered_pools,
+ p_from_block,
+ p_to_block,
+ block_gap,
+ Path::new("../data/prices/prices_deg_5_liq_100_block_18_grad.parquet")
+ ).await.unwrap();
+
+ info!("Done len prices: {:?}", prices.len());
+
+ Ok(())
+
+}
diff --git a/block_extractor/src/pools.rs b/block_extractor/src/pools.rs
@@ -0,0 +1,355 @@
+#![allow(dead_code)]
+#![allow(unused_variables)]
+#![allow(unused_imports)]
+#![allow(unused_mut)]
+
+use alloy::{
+ primitives::{address, Address, FixedBytes},
+ providers::{Provider, RootProvider},
+ rpc::types::{BlockId, BlockTransactionsKind, Filter},
+ sol_types::SolEvent,
+ transports::{http::{Client, Http}, BoxTransport}
+};
+use std::{
+ collections::{BTreeMap, HashMap},
+ fs::OpenOptions,
+ path::Path,
+ str::FromStr,
+ sync::Arc
+};
+use indicatif::{ProgressBar, ProgressStyle};
+use anyhow::{Result, anyhow};
+use csv::StringRecord;
+use log::info;
+
+use crate::interfaces::*;
+
+
+#[derive(Debug, Clone)]
+pub enum Version {
+ V2,
+ V3
+}
+
+#[derive(Debug, Clone)]
+pub struct Pool {
+ pub id: i64,
+ pub address: Address,
+ pub version: Version,
+ pub token0: Address,
+ pub token1: Address,
+ pub fee: u32,
+ pub block_number: u64,
+ pub timestamp: u64,
+ pub tickspacing: i32,
+}
+
+impl From<StringRecord> for Pool {
+ fn from(record: StringRecord) -> Self {
+ let version = match record.get(2).unwrap().parse().unwrap() {
+ 2 => Version::V2,
+ _ => Version::V3
+ };
+ Self {
+ id: record.get(0).unwrap().parse().unwrap(),
+ address: Address::from_str(record.get(1).unwrap()).unwrap(),
+ version,
+ token0: Address::from_str(record.get(3).unwrap()).unwrap(),
+ token1: Address::from_str(record.get(4).unwrap()).unwrap(),
+ fee: record.get(5).unwrap().parse().unwrap(),
+ block_number: record.get(6).unwrap().parse().unwrap(),
+ timestamp: record.get(7).unwrap().parse().unwrap(),
+ tickspacing: record.get(8).unwrap().parse().unwrap(),
+ }
+ }
+}
+
+
+impl Pool {
+ pub fn cache_row(&self) -> (i64, String, i32, String, String, u32, u64, u64, i32) {
+ (
+ self.id,
+ format!("{:?}", self.address),
+ match self.version {
+ Version::V2 => 2,
+ _ => 3,
+ },
+ format!("{:?}", self.token0),
+ format!("{:?}", self.token1),
+ self.fee,
+ self.block_number,
+ self.timestamp,
+ self.tickspacing,
+ )
+ }
+
+ pub fn has_token(&self, token: Address) -> bool {
+ self.token0 == token || self.token1 == token
+ }
+}
+
+pub async fn load_pools(
+ provider: RootProvider<BoxTransport>,
+ path: &Path,
+ from_block: u64,
+ chunk: u64,
+) -> Result<(BTreeMap<Address, Pool>, i64)> {
+
+ info!("Loading Pools...");
+
+ let mut pools = BTreeMap::new();
+ let mut blocks = vec![];
+
+ let file = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .create(true)
+ .open(path)
+ .unwrap();
+
+ let mut writer = csv::Writer::from_writer(file);
+
+ if path.exists() {
+ let mut reader = csv::Reader::from_path(path)?;
+ for row in reader.records() {
+ let row = row.unwrap();
+ let pool = Pool::from(row);
+ blocks.push(pool.block_number);
+ pools.insert(pool.address, pool);
+ }
+ } else {
+ writer.write_record(&[
+ "id",
+ "address",
+ "version",
+ "token0",
+ "token1",
+ "fee",
+ "block_number",
+ "timestamp",
+ "tickspacing",
+ ])?;
+ }
+
+ let last_id = match pools.len() > 0{
+ true => pools.last_key_value().unwrap().1.id,
+ false => -1
+ };
+
+ let from_block = match last_id != -1 {
+ true => {
+ match blocks.iter().max() {
+ Some(b) => *b,
+ None => { return Err(anyhow!("load_pools could not find last processed block")); }
+ }
+ }
+ false => from_block
+ };
+
+
+ let to_block = provider.get_block_number().await.unwrap();
+// let from_block = to_block;
+ let mut processed_blocks = 0u64;
+ let mut block_range: Vec<(u64, u64)> = vec![];
+
+ info!("From block {:?} -> To block {:?}", from_block, to_block);
+
+ loop {
+ let start_idx = from_block + processed_blocks;
+ let mut end_idx = start_idx + chunk - 1;
+ if end_idx > to_block {
+ end_idx = to_block;
+ block_range.push((start_idx, end_idx));
+ break;
+ }
+ block_range.push((start_idx, end_idx));
+ processed_blocks += chunk;
+ }
+
+ let sigs = vec![
+ PoolCreated::SIGNATURE_HASH, // v3
+ PairCreated::SIGNATURE_HASH, // v3
+ ];
+
+ let factories = vec![
+ address!("0x1F98431c8aD98523631AE4a59f267346ea31F984"),
+ address!("0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"),
+ ];
+
+
+ let pb = ProgressBar::new(to_block-from_block);
+ pb.set_style(
+ ProgressStyle::with_template(
+ "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} current pools: {msg}",
+ )
+ .unwrap()
+ .progress_chars("##-"),
+ );
+ pb.inc(0);
+
+ for range in block_range {
+ match get_pool_data(
+ provider.clone(),
+ range.0,
+ range.1,
+ sigs.clone(),
+ factories.clone(),
+ ).await {
+ Ok(r) => {
+ for p in r { pools.insert(p.address, p); }
+ }
+ Err(e) => {
+ info!("get_pool_data call error {:?}", e);
+ continue;
+ }
+ };
+ pb.inc(chunk);
+ pb.set_message(format!("{:?} block range {:?}-{:?}", pools.len(), range.0, range.1));
+ }
+
+ let mut id = 0;
+ let mut added = 0;
+
+ for (_, pool) in pools.iter_mut() {
+ if pool.id == -1 {
+ id += 1;
+ pool.id = id;
+ }
+ if (pool.id as i64) > last_id {
+ writer.serialize(pool.cache_row())?;
+ added += 1;
+ }
+ }
+ writer.flush()?;
+
+ Ok((pools, last_id))
+}
+
+
+async fn get_pool_data(
+ provider: RootProvider<BoxTransport>,
+ from_block: u64,
+ to_block: u64,
+ sig_hash: Vec<FixedBytes<32>>,
+ address: Vec<Address>,
+) -> Result<Vec<Pool>> {
+ let mut pools = Vec::new();
+ let mut timestamp_map: HashMap<u64, u64> = HashMap::new();
+
+ let filter = Filter::new()
+ .from_block(from_block)
+ .to_block(to_block)
+ .event_signature(sig_hash)
+ .address(address);
+
+ let logs = match provider.get_logs(&filter).await {
+ Ok(r) => r,
+ Err(e) => {
+ info!("Error getting logs {:?}", e);
+ return Ok(pools);
+ },
+ };
+
+ for log in logs {
+ let (version, address, token0, token1, fee, tickspacing) = match log.topic0().unwrap() {
+ &PairCreated::SIGNATURE_HASH => {
+ let event = match PairCreated::decode_log_data(
+ log.data(), true
+ ) {
+ Ok(r) => r,
+ Err(e) => {
+ info!("UniswapV2Factory decoding error {:?}", e);
+ continue;
+ }
+ };
+ let tickspacing: i32 = 0;
+ let fee: u32 = 3000;
+ (Version::V2, event.pair, event.token0, event.token1, fee, tickspacing)
+ },
+ &PoolCreated::SIGNATURE_HASH => {
+ let event = match PoolCreated::decode_log_data(
+ log.data(), true
+ ) {
+ Ok(r) => r,
+ Err(e) => {
+ info!("UniswapV3Factory decoding error {:?}", e);
+ continue;
+ }
+ };
+ (Version::V3, event.pool, event.token0, event.token1, event.fee.to::<u32>(), event.tickSpacing.as_i32())
+ },
+ t => {
+ info!("Counld not match topic {:?}", t);
+ continue;
+ }
+ };
+
+ let block_number = match log.block_number {
+ Some(r) => r,
+ None => {
+ info!("log does not contain block_number");
+ 0u64
+ }
+ };
+
+ let timestamp = if !timestamp_map.contains_key(&block_number) {
+ let block = match provider.get_block(
+ BlockId::from(block_number),
+ BlockTransactionsKind::default()
+ ).await {
+ Ok(r) => {
+ match r {
+ Some(v) => v,
+ None => {
+ info!("No block returned");
+ continue;
+ }
+ }
+ },
+ Err(e) => {
+ info!("Could not get block {:?}", e);
+ continue;
+ }
+ };
+ let timestamp = block.header.timestamp;
+ timestamp
+ } else {
+ let timestamp = *timestamp_map.get(&block_number).unwrap();
+ timestamp
+ };
+
+ let pool_data = Pool {
+ id: -1,
+ address,
+ version,
+ token0,
+ token1,
+ fee,
+ block_number,
+ timestamp,
+ tickspacing
+ };
+
+ pools.push(pool_data)
+ }
+ Ok(pools)
+}
+
+pub fn load_pools_from_file(
+ path: &Path,
+) -> Result<BTreeMap<Address, Pool>> {
+ let mut pools = BTreeMap::new();
+
+ if path.exists() {
+ let mut reader = csv::Reader::from_path(path)?;
+ for row in reader.records() {
+ let row = row.unwrap();
+ let pool = Pool::from(row);
+ pools.insert(pool.address, pool);
+ }
+ } else {
+ return Err(anyhow!("File path does not exist"));
+ }
+
+ Ok(pools)
+}
diff --git a/block_extractor/src/prices.rs b/block_extractor/src/prices.rs
@@ -0,0 +1,262 @@
+use alloy::{
+ eips::BlockId,
+ primitives::{Address, U256},
+ providers::RootProvider,
+ pubsub::PubSubFrontend, transports::BoxTransport,
+};
+use arrow::{
+ array::ArrayRef, datatypes::{DataType, Field, Schema}, record_batch::RecordBatch
+};
+use parquet::{
+ arrow::{arrow_reader::ParquetRecordBatchReaderBuilder, ArrowWriter}, basic::Compression, file::properties::{EnabledStatistics, WriterProperties}
+};
+use indicatif::{ProgressBar, ProgressStyle};
+use std::{
+ collections::BTreeMap,
+ fs::{OpenOptions, File},
+ path::Path,
+ sync::Arc
+};
+use log::info;
+use anyhow::{anyhow, Result};
+
+use crate::{interfaces::*, pools::{Pool, Version}};
+
+pub struct Price {
+ pool: Address,
+ block: u64,
+ r_t0: Option<U256>,
+ r_t1: Option<U256>,
+ spx96: Option<U256>,
+}
+
+
+pub async fn load_prices(
+ provider: RootProvider<BoxTransport>,
+ pools: &BTreeMap<Address, Pool>,
+ from_block: u64,
+ to_block: u64,
+ block_gap: u64,
+ path : &Path,
+) -> Result<Vec<Price>> {
+
+ let mut prices = Vec::new();
+
+
+ let mut blocks = Vec::new();
+ blocks.push(from_block);
+ let mut cur = from_block;
+ loop {
+ cur += block_gap;
+ if cur > to_block {
+ blocks.push(to_block);
+ break
+ }
+ blocks.push(cur);
+ }
+
+ let pb = ProgressBar::new(blocks.len() as u64);
+ pb.set_style(
+ ProgressStyle::with_template(
+ "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
+ )
+ .unwrap()
+ .progress_chars("##-"),
+ );
+ pb.set_message(format!("From block {:?} - To Block {:?}", from_block, to_block));
+ pb.inc(0);
+
+ for block in blocks {
+ 'pool_loop: for (_, pool) in pools.into_iter() {
+ match pool.version {
+ Version::V2 => {
+ match get_v2_price(
+ provider.clone(),
+ block,
+ pool,
+ ).await {
+ Ok(price) => {
+ prices.push(
+ Price {
+ pool: pool.address,
+ block,
+ r_t0: Some(price.0),
+ r_t1: Some(price.1),
+ spx96: None,
+ }
+ );
+ }
+ Err(e) => {
+ info!("Error getting price {:?}", e);
+ continue 'pool_loop;
+ }
+ };
+ }
+ Version::V3 => {
+ match get_v3_price(
+ provider.clone(),
+ block,
+ pool
+ ).await {
+ Ok(price) => {
+ prices.push(
+ Price {
+ pool: pool.address,
+ block,
+ r_t0: Some(price.0),
+ r_t1: Some(price.1),
+ spx96: Some(price.2),
+ }
+ );
+ }
+ Err(e) => {
+ info!("Error getting price {:?}", e);
+ continue 'pool_loop;
+ }
+ };
+ }
+ }
+ }
+ pb.inc(1)
+ }
+
+ let file = OpenOptions::new()
+ .write(true)
+ .truncate(true)
+ .create(true)
+ .open(path)
+ .unwrap();
+
+ let props = WriterProperties::builder()
+ .set_compression(Compression::SNAPPY)
+ .set_statistics_enabled(EnabledStatistics::None)
+ .build();
+
+ let batch = create_record_batch(&prices).unwrap();
+ println!("{:?}", batch.schema());
+
+ let mut writer = ArrowWriter::try_new(file, batch.schema(), Some(props)).unwrap();
+ writer.write(&batch).unwrap();
+ writer.close().unwrap();
+
+ Ok(prices)
+}
+
+fn create_record_batch(
+ prices: &Vec<Price>,
+) -> Result<RecordBatch> {
+
+ let pools = prices.iter()
+ .map(|p| format!("{:?}", p.pool))
+ .collect::<Vec<String>>();
+
+ let blocks = prices.iter()
+ .map(|p| p.block as i64)
+ .collect::<Vec<i64>>();
+
+ let r_t0s = prices.iter()
+ .map(|p| match p.r_t0{
+ Some(r) => Some(format!("{:?}", r)),
+ None => None,
+ }).collect::<Vec<Option<String>>>();
+
+ let r_t1s = prices.iter()
+ .map(|p| match p.r_t1{
+ Some(r) => Some(format!("{:?}", r)),
+ None => None,
+ })
+ .collect::<Vec<Option<String>>>();
+
+ let spx96s = prices.iter()
+ .map(|p| match p.spx96 {
+ Some(r) => Some(format!("{:?}", r)),
+ None => None,
+ })
+ .collect::<Vec<Option<String>>>();
+
+ let batch = RecordBatch::try_from_iter(
+ vec![
+ ("pool_address", Arc::new(arrow::array::StringArray::from(pools)) as ArrayRef),
+ ("block_number", Arc::new(arrow::array::Int64Array::from(blocks)) as ArrayRef),
+ ("reserve_t0", Arc::new(arrow::array::StringArray::from(r_t0s)) as ArrayRef),
+ ("reserve_t1", Arc::new(arrow::array::StringArray::from(r_t1s)) as ArrayRef),
+ ("sqrt_price_x96", Arc::new(arrow::array::StringArray::from(spx96s)) as ArrayRef),
+ ]
+ ).unwrap();
+
+ Ok(batch)
+}
+
+async fn get_v2_price(
+ provider: RootProvider<BoxTransport>,
+ block_number: u64,
+ pool: &Pool,
+) -> Result<(U256, U256)> {
+
+ let block = BlockId::from(block_number);
+
+ let token0_ierc20 = IERC20::new(pool.token0, &provider); // token1
+ let token1_ierc20 = IERC20::new(pool.token1, &provider); // token1
+
+ let balance_token0 = match token0_ierc20
+ .balanceOf(pool.address)
+ .block(block)
+ .call()
+ .await {
+ Ok(r) => r.balance,
+ Err(e) => { return Err(anyhow!("Error getting balance_token0 {:?}", e)); }
+ };
+
+ let balance_token1 = match token1_ierc20
+ .balanceOf(pool.address)
+ .block(block)
+ .call()
+ .await {
+ Ok(r) => r.balance,
+ Err(e) => { return Err(anyhow!("Error getting balance_token1 {:?}", e)); }
+ };
+
+ return Ok((balance_token0, balance_token1));
+}
+
+async fn get_v3_price(
+ provider: RootProvider<BoxTransport>,
+ block_number: u64,
+ pool: &Pool,
+) -> Result<(U256, U256, U256)> {
+
+ let block = BlockId::from(block_number);
+
+ let token0_ierc20 = IERC20::new(pool.token0, &provider); // token1
+ let token1_ierc20 = IERC20::new(pool.token1, &provider); // token1
+ let pool_int = IUniswapV3Pool::new(pool.address, &provider); // token1
+
+
+ let balance_token0 = match token0_ierc20
+ .balanceOf(pool.address)
+ .block(block)
+ .call()
+ .await {
+ Ok(r) => r.balance,
+ Err(e) => { return Err(anyhow!("Error getting balance_token0 {:?}", e)); }
+ };
+ let balance_token1 = match token1_ierc20
+ .balanceOf(pool.address)
+ .block(block)
+ .call()
+ .await {
+ Ok(r) => r.balance,
+ Err(e) => { return Err(anyhow!("Error getting balance_token1 {:?}", e)); }
+ };
+
+ let sqrt_price_x96 = match pool_int
+ .slot0()
+ .block(block)
+ .call()
+ .await {
+ Ok(r) => U256::from(r.sqrtPriceX96),
+ Err(e) => { return Err(anyhow!("Error returning sqrt_price_x96 {:?}", e)); }
+ };
+
+ return Ok((balance_token0, balance_token1, sqrt_price_x96));
+}
diff --git a/block_extractor/src/tokens.rs b/block_extractor/src/tokens.rs
@@ -0,0 +1,226 @@
+#![allow(dead_code)]
+#![allow(unused_variables)]
+#![allow(unused_imports)]
+#![allow(unused_mut)]
+
+use std::{
+ collections::BTreeMap,
+ fs::OpenOptions,
+ path::Path,
+ str::FromStr
+};
+use alloy::{
+ primitives::Address,
+ providers::RootProvider,
+ transports::{http::{Client, Http}, BoxTransport},
+};
+use indicatif::{ProgressBar, ProgressStyle};
+use anyhow::{anyhow, Result};
+use csv::StringRecord;
+use log::info;
+
+use crate::{interfaces::IERC20, pools::Pool};
+
+
+#[derive(Debug, Clone)]
+pub struct Token {
+ pub id: i64,
+ pub address: Address,
+ pub name: String,
+ pub symbol: String,
+ pub decimals: u8
+}
+
+impl From<StringRecord> for Token {
+ fn from(record: StringRecord) -> Self {
+ Self {
+ id: record.get(0).unwrap().parse().unwrap(),
+ address: Address::from_str(record.get(1).unwrap()).unwrap(),
+ name: String::from(record.get(2).unwrap()),
+ symbol: String::from(record.get(3).unwrap()),
+ decimals: record.get(4).unwrap().parse().unwrap(),
+ }
+ }
+}
+
+impl Token {
+ pub fn cache_row(&self) -> (i64, String, String, String, u8) {
+ (
+ self.id,
+ format!("{:?}", self.address),
+ self.name.clone(),
+ self.symbol.clone(),
+ self.decimals,
+ )
+ }
+}
+
+pub async fn load_tokens(
+ provider: RootProvider<BoxTransport>,
+ path: &Path,
+ pools: &BTreeMap<Address, Pool>,
+ parallel: u64,
+ last_pool_id: i64,
+) -> Result<BTreeMap<Address, Token>> {
+
+ info!("Loading tokens...");
+
+ let mut tokens = BTreeMap::new();
+
+ let file = OpenOptions::new()
+ .write(true)
+ .append(true)
+ .create(true)
+ .open(path)
+ .unwrap();
+
+ let mut writer = csv::Writer::from_writer(file);
+
+ let mut token_id = 0;
+ if path.exists() {
+ let mut reader = csv::Reader::from_path(path)?;
+ for row in reader.records() {
+ let row = row.unwrap();
+ let token = Token::from(row);
+ tokens.insert(token.address, token);
+ token_id += 1;
+ }
+ } else {
+ writer.write_record(&[
+ "id",
+ "address",
+ "name",
+ "symbol",
+ "decimals",
+ ])?;
+ }
+
+ let pb = ProgressBar::new(pools.len() as u64);
+ pb.set_style(
+ ProgressStyle::with_template(
+ "[{elapsed_precise}] {bar:40.cyan/blue} {pos:>7}/{len:7} {msg}",
+ )
+ .unwrap()
+ .progress_chars("##-"),
+ );
+
+ let new_token_id = token_id;
+
+ let mut count = 0;
+ let mut requests = Vec::new();
+
+ for (_, pool) in pools.into_iter() {
+ let pool_id = pool.id;
+ if pool_id <= last_pool_id {
+ continue;
+ }
+ let token0 = pool.token0;
+ let token1 = pool.token1;
+ for token in [token0, token1] {
+ if !tokens.contains_key(&token) {
+ requests.push(
+ tokio::task::spawn(
+ get_token_data(
+ provider.clone(),
+ token,
+ )
+ )
+ );
+ count += 1 ;
+ }
+ if count == parallel {
+ let results = futures::future::join_all(requests).await;
+ for result in results {
+ match result {
+ Ok(r) => match r {
+ Ok(t) => {
+ tokens.insert(
+ t.address,
+ Token {
+ id: token_id,
+ address: t.address,
+ name: t.name,
+ symbol: t.symbol,
+ decimals: t.decimals
+ }
+ );
+ token_id += 1
+ }
+ Err(e) => { info!("Something wrong 0 {:?}", e) }
+ }
+ Err(e) => { info!("Something wrong 1 {:?}", e) }
+ }
+ }
+ requests = Vec::new();
+ count = 0;
+ pb.inc(parallel);
+ }
+ }
+ }
+
+ let mut added = 0;
+ for token in tokens.values().collect::<Vec<&Token>>().iter() {
+ if token.id >= new_token_id {
+ writer.serialize(token.cache_row())?;
+ added += 1
+ }
+ }
+ writer.flush()?;
+
+ Ok(tokens)
+}
+
+async fn get_token_data(
+ provider: RootProvider<BoxTransport>,
+ token: Address,
+) -> Result<Token> {
+
+ let interface = IERC20::new(token, provider);
+
+ let decimals = match interface.decimals().call().await {
+ Ok(r) => r.decimals,
+ Err(e) => { return Err(anyhow!("Decimals of token failed {:?}", e )) }
+ };
+
+ let name = match interface.name().call().await {
+ Ok(r) => r.name,
+ Err(e) => {
+ info!("Name of token {:?} failed {:?}", token, e);
+ String::from("PlaceHolderName")
+ }
+ };
+ let symbol = match interface.symbol().call().await{
+ Ok(r) => r.symbol,
+ Err(e) => {
+ info!("Symbol of token failed {:?}", e );
+ String::from("PlaceHolderSymbol")
+ }
+ };
+
+ Ok(Token {
+ id: -1,
+ address: token,
+ name,
+ symbol,
+ decimals,
+ })
+}
+
+pub fn load_tokens_from_file(
+ path: &Path,
+) -> Result<BTreeMap<Address, Token>> {
+ let mut tokens = BTreeMap::new();
+
+ if path.exists() {
+ let mut reader = csv::Reader::from_path(path)?;
+ for row in reader.records() {
+ let row = row.unwrap();
+ let token = Token::from(row);
+ tokens.insert(token.address, token);
+ }
+ } else {
+ return Err(anyhow!("File path does not exist"));
+ }
+
+ Ok(tokens)
+}
diff --git a/rl_arb/README.md b/rl_arb/README.md
@@ -0,0 +1 @@
+This code solves the n-cyclic arbitrage routing problem with RL.
diff --git a/rl_arb/main.py b/rl_arb/main.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+
+from rl_arb.cli import run
+
+def main():
+ run()
+
+if __name__ == '__main__':
+ main()
diff --git a/rl_arb/rl_arb/__init__.py b/rl_arb/rl_arb/__init__.py
@@ -0,0 +1 @@
+#!/usr/bin/env python3
diff --git a/rl_arb/rl_arb/brute_force.py b/rl_arb/rl_arb/brute_force.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python3
+
+import numpy as np
+import torch
+import time
+import os
+import sys
+import pickle
+from torch_geometric.data import Data, Batch
+from torch_geometric.nn import summary
+
+from rl_arb.mcts import MCTSParallel, PMemory
+from rl_arb.utils import send_telegram_message
+sys.setrecursionlimit(1000000)
+
+from rl_arb.initializer import Initializer
+from rl_arb.config import ARGS_TRAINING, DEVICE
+from rl_arb.logger import logging
+logger = logging.getLogger('rl_circuit')
+
+def brute_force_search_trail(mdp, source, cap):
+ """
+ Brute force solution of the MDP problem, by doing recursive depth first
+ search and capping the length of the state by 'cap'
+
+ Parameters
+ ---------
+ mdp: MDP
+ Class of of the MDP.
+ source: int
+ Source node.
+ cap: int
+ Boundary on solution length.
+
+ Returns
+ -------
+ tuple
+ - list of solutions
+ - list of values of solutions performance (profit)
+ """
+ def dfs(state):
+# if len(state) > cap:
+# return
+
+ if mdp.check_win(state):
+ trails.append(state)
+ return
+
+ valid_actions = mdp.get_valid_actions(state)
+ for action in valid_actions:
+ dfs(state + [action])
+ return
+
+ trails = []
+ state = [(source, source, 0)]
+ dfs(state)
+ profits = [0]
+ for trail in trails:
+ profits.append(mdp.calculate_profit(trail, mdp.current_block))
+
+ return trails, np.max(profits)
+
+
+@torch.no_grad()
+def test_model():
+ """
+ Testing function
+ """
+ problem = Initializer()
+
+ problem.mdp.data.to(DEVICE)
+ problem.mdp.device = DEVICE
+ problem.model.to(DEVICE)
+ problem.model.share_memory()
+ problem.model.eval()
+
+ mcts_parallel = MCTSParallel(problem.mdp, ARGS_TRAINING)
+
+ if not os.path.exists('./test'):
+ os.mkdir('./test')
+
+ loss = {}
+ values = {}
+ brute_values = {}
+ np.random.seed(0)
+ selected_blocks = np.random.choice(problem.mdp.num_blocks, 100, replace=False)
+
+ start = problem.mdp.start_node
+ times = []
+ for b in selected_blocks:
+ problem.mdp.current_block = b
+ problem.mcts.mdp.current_block = b
+ st = time.time()
+ _, brute_profit = brute_force_search_trail(problem.mdp, start, 10)
+ et = time.time()
+ print(et-st)
+ times.append(et-st)
+ brute_values[b] = np.log(brute_profit)
+ print(np.mean(times))
+ exit()
+
+ for i in range(100):
+# problem.model.load_state_dict(
+# torch.load(
+# f"./model/model_{i}.pt",
+# weights_only = True,
+# map_location=DEVICE
+# ),
+# strict = False
+# )
+
+ logger.info(f"Iterations {i}/100")
+# send_telegram_message(f"Iterations {i}/100")
+ loss[i] = {}
+ values[i] = {}
+
+ step = 25
+ for block in range(step, len(selected_blocks)+step, step):
+
+ p_memory = [
+ PMemory(mcts_parallel.mdp, b) for b in selected_blocks[block-step: block]
+ ]
+
+ st = time.time()
+ while len(p_memory) > 0:
+ states = [mem.state for mem in p_memory]
+
+ mcts_parallel.search(problem.model, states, p_memory)
+ for m in range(len(p_memory))[::-1]:
+ mem = p_memory[m]
+ probs = np.zeros(len(mcts_parallel.mdp.edge_list))
+
+ for child in mem.root.children:
+ probs[
+ mcts_parallel.mdp.edge_list.index(child.action_taken)
+ ] = child.value_best
+
+ if np.sum(probs) == 0:
+ for child in mem.root.children:
+ probs[
+ mcts_parallel.mdp.edge_list.index(child.action_taken)
+ ] = child.visit_count
+
+ probs /= np.sum(probs)
+
+ action = mcts_parallel.mdp.edge_list[np.argmax(probs)]
+ mem.state = mcts_parallel.mdp.get_next_state(mem.state, action)
+
+ value, is_terminal = mcts_parallel.mdp.get_value_and_terminated(
+ mem.state,
+ mem.current_block
+ )
+
+ if is_terminal:
+ cb = mem.current_block
+ values[i][cb] = value
+ loss[i][cb] = 1-value/brute_values[cb]
+
+ del p_memory[m]
+ et = time.time()
+ print((et-st)/25)
+
+
+ logger.info(f"AVERAGE loss {np.mean([loss[i][k] for k in loss[i].keys()])}")
+# send_telegram_message(f"AVERAGE loss {np.mean([loss[i][k] for k in loss[i].keys()])}")
+
+ with open(f'./test/test.pickle', "wb") as f:
+ pickle.dump([loss, values, brute_values], f)
+
+
diff --git a/rl_arb/rl_arb/cli.py b/rl_arb/rl_arb/cli.py
@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+
+import sys
+import os
+import subprocess
+
+from rl_arb.initializer import Initializer
+from rl_arb.utils import print_summary, send_telegram_message
+from rl_arb.brute_force import test_model
+from rl_arb.logger import logging
+logger = logging.getLogger('rl_circuit')
+
+def run():
+ """
+ Small cli for interaction.
+ """
+ logger.info("Started...")
+ help = """
+ flags:
+ learn: initializes the training process
+
+ test: Does some testing with the model.
+
+ vsc5: intitializes the training on the vsc5 node
+
+ mycomp: intitializes the training on the private comp
+ """
+
+ if sys.argv[1] == "mycomp":
+ hostname = os.uname()[1]
+ if hostname == "frame":
+ cwd = os.getcwd()
+ logger.info("Copying code...")
+ subprocess.getoutput(f"rsync -Pr {cwd} mycomp:~/")
+ logger.info("Executing code remotely...")
+ if sys.argv[2] == "test":
+ cmd = "test"
+ else:
+ cmd = "learn"
+ with subprocess.Popen(
+ f"ssh -4 mycomp 'source py_env/bin/activate && cd ./rl_arb && ./main.py {cmd}'",
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ shell=True
+ ) as process:
+ for line in process.stdout:
+ logger.info(line.decode('utf8'))
+ else:
+ logger.info("Not on frame, closing...")
+
+ elif sys.argv[1] == "vsc5":
+ cwd = os.getcwd()
+ data_path = "/gpfs/data/fs70700/miksa234"
+ logger.info("Copying code...")
+ subprocess.getoutput(f"rsync -Pr {cwd} vsc5:{data_path}")
+ logger.info(subprocess.getoutput(f"ssh -4 vsc5 'cd {data_path} && sbatch rl.job'"))
+
+ elif sys.argv[1] == "learn":
+ problem = Initializer()
+ problem.rlearn.learn(problem.optimizer)
+
+ elif sys.argv[1] == "reinforce":
+ problem = Initializer()
+ problem.reinforce.learn()
+
+ elif sys.argv[1] == "info":
+ problem = Initializer()
+ print_summary(problem)
+
+ elif sys.argv[1] == "test":
+ test_model()
+
+ elif sys.argv[1] == "help":
+ logger.info(help)
+
+ else:
+ logger.info("No valid input detected")
+ logger.info(help)
+
+ logger.info("END")
diff --git a/rl_arb/rl_arb/config.py b/rl_arb/rl_arb/config.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+
+import torch
+from dotenv import dotenv_values
+
+env = dotenv_values(".env")
+
+TELEGRAM_TOKEN = env["TELEGRAM_TOKEN"]
+TELEGRAM_CHAT_ID = env["TELEGRAM_CHAT_ID"]
+TELEGRAM_SEND_URL = f'https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage'
+
+DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
+
+WETH = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
+
+ARGS_GAME = {
+ 'tau': 1,
+ 'M': 1,
+ 'cutoff': 25
+}
+
+ARGS_MODEL = {
+ 'in_channels': 4,
+ 'emb_channels': 128,
+ 'num_heads': 4,
+ 'num_layers': 4,
+ 'ff_dim': 512,
+ 'policy_mheads': 2,
+}
+
+ARGS_TRAINING = {
+ 'C': 1.4,
+ 'num_reinforce': 100000,
+ 'num_iterations': 100,
+ 'num_searches': 100,
+ 'num_rollouts': 100,
+ 'num_self_play_iterations': 200,
+ 'num_parallel': 10,
+ 'num_epochs': 1,
+ 'batch_size': 25,
+ 'gamma': 0.98,
+ 'temperature': 1.25,
+ 'eps': 0.25,
+ 'dirichlet_alpha': 0.03,
+ 'num_processes': 20,
+ 'multicore': True,
+ 'telegram': True,
+}
diff --git a/rl_arb/rl_arb/initializer.py b/rl_arb/rl_arb/initializer.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+
+import os
+import networkx as nx
+import pandas as pd
+import numpy as np
+import torch
+from torch_geometric.data import Data
+import logging
+
+logger = logging.getLogger('rl_circuit')
+import torch.multiprocessing as mp
+try:
+ mp.set_start_method('spawn', force=True)
+except RuntimeError:
+ pass
+
+from rl_arb.mdp import MDP
+from rl_arb.mcts import MCTS
+from rl_arb.net import PolicyNet
+from rl_arb.rlearn import AgentRLearn
+from rl_arb.reinforce import Reinforce
+from rl_arb.config import (
+ DEVICE,
+ ARGS_GAME,
+ ARGS_MODEL,
+ ARGS_TRAINING,
+ WETH
+)
+from rl_arb.utils import (
+ load_pools_and_tokens,
+ make_token_graph,
+ linear_node_relabel,
+ filter_pools_with_no_gradient,
+ send_telegram_message
+)
+
+
+class Initializer():
+ """
+ Initializer object for the problem to load all the importaint variables
+ through a class
+
+ Attributes:
+ ----------
+ pools: pd.DataFrame
+ Filtered pools data used for the problem.
+ tokens: pd.DataFrame
+ All tokens found from all pools.
+ prices: pd.DataFrame
+ Prices of the used pools in a time interval.
+ graph: nx.MultiDiGraph
+ Problem graph, where edges are pools, nodes tokens.
+ token_mapping: dict
+ Linear node relabeling of eth address to index.
+ line_graph: nx.Graph
+ The line graph of 'graph' object.
+ line_mapping: dict
+ Linear node relabeling.
+ mdp: MDP
+ Network Markov Decision Process to be explored for arbitrage.
+ model: torch.nn.Module
+ Torch geometric deep graph neural network.
+ optimizer: torch.optim.Adam
+ Torch optimizer used to update the weights.
+ rlearn: AgentRLearn
+ Mcts based reinforcement learning algorithm.
+ mcts: MCTS
+ Monte Carlo Tree search algorithm.
+
+ """
+ def __init__(self):
+ pools, tokens = load_pools_and_tokens(
+ '../data/pools/pools_deg_5_liq_100_block_18_grad.csv',
+ '../data/tokens/tokens.csv',
+ )
+ prices = pd.read_parquet(
+ '../data/prices/prices_deg_5_liq_100_block_18_grad.parquet'
+ )
+ pools, prices = filter_pools_with_no_gradient(pools, prices)
+
+ G = make_token_graph(pools, prices)
+ G, token_mapping = linear_node_relabel(G)
+
+ L = nx.line_graph(G, create_using=nx.Graph)
+ for e in G.edges(data=True):
+ nx.set_node_attributes(
+ L,
+ {(e[0], e[1], e[2]['k']): e[2]['weight']},
+ name='mexr'
+ )
+ nx.set_node_attributes(
+ L,
+ {(e[0], e[1], e[2]['k']): e[2]['address']},
+ name='address'
+ )
+ L, line_mapping = linear_node_relabel(L)
+
+ edges = list(G.edges(data=True))
+ for (t0, t1, d) in edges:
+ inverse_weights = [1/el for el in d['weight']]
+ G.add_edge(t1, t0, k=d['k'], weight=inverse_weights, address=d['address'])
+
+
+ edge_list = [list(e) for e in L.edges()]
+ rates = np.array([np.log(n[1]['mexr']) for n in L.nodes(data=True)]).T
+ used = [0 for _ in range(len(L.nodes))]
+ t0_using = [0 for _ in range(len(L.nodes))]
+ t1_using = [0 for _ in range(len(L.nodes))]
+
+ node_attr = torch.tensor(np.dstack([*rates, used, t0_using, t1_using])).float().squeeze(0)
+ edge_index = torch.tensor(edge_list).t().contiguous()
+ data = Data(x=node_attr, edge_index=edge_index)
+
+ mdp = MDP(G, data, line_mapping, ARGS_GAME, start_node=token_mapping[WETH])
+
+ model = PolicyNet(
+ ARGS_MODEL
+ ).share_memory().to(DEVICE)
+
+ optimizer = torch.optim.Adam(model.parameters(), lr=1e-4, weight_decay=1e-4)
+ optimizer.zero_grad()
+
+ mcts = MCTS(mdp, ARGS_TRAINING, model)
+
+ rlearn = AgentRLearn(model, mdp, ARGS_TRAINING)
+
+ reinforce = Reinforce(model, optimizer, mdp, mcts, ARGS_TRAINING)
+
+ if not os.path.exists('./model'):
+ os.mkdir('./model')
+
+ if not os.path.exists('./baseline'):
+ os.mkdir('./baseline')
+
+ self.pools = pools
+ self.tokens = tokens
+ self.prices = prices
+
+ self.graph = G
+ self.token_mapping = token_mapping
+ self.line_graph = L
+ self.line_mapping = line_mapping
+ self.graph_data = data
+
+ self.mdp = mdp
+ self.model = model
+ self.optimizer = optimizer
+ self.rlearn = rlearn
+ self.reinforce = reinforce
+ self.mcts = mcts # not parallel version
diff --git a/rl_arb/rl_arb/logger.py b/rl_arb/rl_arb/logger.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python3
+
+import logging
+import sys
+
+format = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s"
+logging.basicConfig(format=format)
+
+logger = logging.getLogger('rl_circuit')
+logger.setLevel(logging.DEBUG)
diff --git a/rl_arb/rl_arb/mcts.py b/rl_arb/rl_arb/mcts.py
@@ -0,0 +1,502 @@
+#!/usr/bin/env python3
+
+import numpy as np
+import torch
+from torch_geometric.data import Batch, Data
+import time
+
+import logging
+
+logger = logging.getLogger('rl_circuit')
+
+class Node:
+ """
+ A class representing a node in the Monte Carlo Tree Search (MCTS).
+
+ Attributes
+ ----------
+ args : dict
+ Arguments for the MCTS.
+ state : object
+ The state of the mdp at this node.
+ parent : Node, optional
+ The parent node of this node.
+ action_taken : object, optional
+ The action taken to reach this node.
+ prior : float, optional
+ The prior probability of selecting this node.
+ visit_count : int, optional
+ The number of times this node has been visited.
+ children : list
+ The list of child nodes.
+ expandable_actions : list
+ The list of actions that can be taken from this node.
+ visit_count: int
+ Number of times visited in the MCTS process.
+ value_best: float
+ The best of values backpropagated through this node.
+ q_value: float
+ Normalized value_best based on parent value_best.
+
+
+ Methods
+ -------
+ is_fully_expanded():
+ Checks if the node is fully expanded.
+ select():
+ Selects the child node with the highest UCB value.
+ select():
+ Selects the child node randomly.
+ get_ucb(child):
+ Calculates the UCB value for a child node.
+ expand(policy, mdp):
+ Expands all possible actions, creating net policy output.
+ simulate(mdp):
+ Simulates a rollout from the current state randomly.
+ backpropagate(value):
+ Backpropagates the value through the tree.
+ """
+ def __init__(
+ self,
+ mdp,
+ args,
+ state,
+ parent=None,
+ action_taken=None,
+ prior=0,
+ visit_count=0,
+ value_best=0,
+ ):
+ self.args = args
+ self.state = state
+ self.parent = parent
+ self.action_taken = action_taken
+ self.prior = prior
+
+ self.children = []
+ self.expandable_actions = mdp.get_valid_actions(state)
+
+ self.visit_count = visit_count
+ self.value_best = value_best
+ self.q_value = 0
+
+
+ def is_fully_expanded(self):
+ """
+ Checks if the node is fully expanded.
+ Node is fully expanded if there are no valid moves,
+ or node is fully expanded if there are children.
+
+ Returns
+ -------
+ bool
+ True if the node is fully expanded, False otherwise.
+ """
+ return len(self.children) > 0
+
+ def select(self):
+ """
+ Calculate ucb for all children.
+ Selects the child node with the highest UCB value.
+
+ Parameters
+ ----------
+ ucb_method : str, optional
+ The method to calculate UCB value. Default is 'alphazero'.
+
+ Returns
+ -------
+ Node
+ The child node with the highest UCB value.
+ """
+ best_child = None
+ best_ucb = -np.inf
+
+ for child in self.children:
+ ucb = self.get_ucb(child)
+ if ucb > best_ucb:
+ best_child = child
+ best_ucb = ucb
+
+ return best_child
+
+ def select_random(self):
+ return np.random.choice(self.children)
+
+ def get_ucb(self, child):
+ """
+ Calculates the UCB value for a child node using the AlphaZero method.
+ Exploration vs Exploitation constant is self.args['C']
+
+ Parameters
+ ----------
+ child : Node
+ The child node.
+
+ Returns
+ -------
+ float
+ The UCB value for the child node using the AlphaZero method.
+ """
+ q_value = child.q_value
+ u_value = np.sqrt(self.visit_count)/(child.visit_count + 1)
+
+ return q_value + self.args['C'] * u_value * child.prior
+
+
+ def expand(self, policy, mdp):
+ """
+ Expands the node using the policy.
+
+ Parameters
+ ----------
+ policy : array-like
+ The policy probabilities for each action.
+ mdp : object
+ The mdp object.
+ """
+ for action, prob in enumerate(policy):
+ if prob > 0:
+ child_state = self.state.copy()
+ child_state = mdp.get_next_state(child_state, mdp.edge_list[action])
+
+ child = Node(mdp, self.args, child_state, self, mdp.edge_list[action], prob)
+ self.children.append(child)
+
+ def simulate(self, mdp, current_block):
+ """
+ Runs a random simulation on the mdp at current state untill terminal,
+ returns the value of the terminal state.
+
+ Parameters
+ ----------
+ mdp: MDP
+ MDP class object.
+ current_block: int
+ Current block index.
+
+ Returns
+ -------
+ value: float
+ State value.
+ """
+ value, terminal = mdp.get_value_and_terminated(
+ self.state,
+ current_block
+ )
+
+ if terminal:
+ return value
+
+ rollout_state = self.state.copy()
+ while True:
+ valid_actions = mdp.get_valid_actions(rollout_state)
+ action = valid_actions[np.random.choice(len(valid_actions))]
+ rollout_state = mdp.get_next_state(rollout_state, action)
+ value, terminal = mdp.get_value_and_terminated(
+ rollout_state,
+ current_block
+ )
+ if terminal:
+ return value
+
+ def backpropagate(self, value):
+ """
+ Backpropagates the value through the tree.
+
+ Parameters
+ ----------
+ value : float
+ The value to backpropagate.
+ """
+ self.visit_count += 1
+
+ if self.parent is not None:
+ self.parent.backpropagate(value)
+ if self.value_best < value:
+ self.q_value = value/self.parent.value_best
+ self.value_best = value
+
+
+
+class MCTS:
+ """
+ A class representing the Monte Carlo Tree Search (MCTS) algorithm.
+
+ Attributes
+ ----------
+ mdp : object
+ The mdp object.
+ args : dict
+ Arguments for the MCTS.
+ model : object
+ The model used for policy and value predictions.
+
+ Methods
+ -------
+ search(state):
+ Performs the MCTS search from the given state.
+ """
+ def __init__(self, mdp, args, model):
+ self.mdp = mdp
+ self.args = args
+ self.model = model
+
+ @torch.no_grad()
+ def search(self, state):
+ """
+ Performs the MCTS search from the given state.
+
+ Parameters
+ ----------
+ state : object
+ The state of the mdp.
+
+ Returns
+ -------
+ array-like
+ The visit count distribution over actions.
+ """
+
+ root = Node(self.mdp, self.args, state, visit_count=1, value_best=1)
+
+ policy = self.model(
+ self.mdp.encode_state(root.state, self.mdp.current_block),
+ self.mdp.data.edge_index,
+ self.mdp.encode_state(root.state[:1], self.mdp.current_block)
+ )
+
+ policy = policy.squeeze(0).detach().cpu()
+
+ policy = self.mdp.mask_policy(policy, root.state)
+
+ root.expand(policy, self.mdp)
+
+ for _ in range(self.args['num_rollouts']):
+ n = root.select_random()
+ v = n.simulate(self.mdp, self.mdp.current_block)
+ n.backpropagate(v)
+
+ for _ in range(self.args['num_searches']):
+ node = root
+ while node.is_fully_expanded():
+ node = node.select()
+
+
+ value, is_terminal = self.mdp.get_value_and_terminated(
+ node.state,
+ self.mdp.current_block
+ )
+
+ if not is_terminal:
+ policy = self.model(
+ self.mdp.encode_state(node.state, self.mdp.current_block),
+ self.mdp.data.edge_index,
+ y=self.mdp.encode_state(node.state[:1], self.mdp.current_block),
+ )
+ policy = policy.squeeze(0)
+ policy = self.mdp.mask_policy(policy, node.state)
+
+ node.expand(policy, self.mdp)
+
+ for _ in range(self.args['num_rollouts']):
+ n = node.select_random()
+ v = n.simulate(self.mdp, self.mdp.current_block)
+ n.backpropagate(v)
+
+ # return visit_count distribution
+ action_probs = np.zeros(len(self.mdp.edge_list))
+ for child in root.children:
+ action_probs[
+ self.mdp.edge_list.index(child.action_taken)
+ ] = child.value_best
+
+ if sum(action_probs) == 0:
+ for child in root.children:
+ action_probs[
+ self.mdp.edge_list.index(child.action_taken)
+ ] = child.visit_count
+
+
+ action_probs /= np.sum(action_probs)
+
+ return action_probs
+
+
+class MCTSParallel:
+ """
+ A class representing the parallel version of
+ the Monte Carlo Tree Search (MCTS) algorithm.
+
+ Attributes
+ ----------
+ mdp : MDP
+ The mdp object.
+ args : dict
+ Arguments for the MCTS.
+
+ Methods
+ -------
+ set_C(C):
+ Sets the exploration parameter for the MCTS.
+ search(model, states, p_memory):
+ Performs the parallel MCTS search from the given states.
+ """
+ def __init__(self, mdp, args):
+ self.mdp = mdp
+ self.args = args
+
+ def set_C(self, C):
+ """
+ Set the exploration parameter for the MCTS algorithm.
+
+ Parameters
+ ----------
+ self : object
+ The instance of the class.
+ C : float
+ The exploration parameter to be set.
+ """
+ self.args['C'] = C
+
+ @torch.no_grad()
+ def search(self, model, states, p_memory):
+ """
+ Performs the parallel MCTS search from the given states.
+
+ Parameters
+ ----------
+ model: Net
+ Deep GNN computes the policy.
+ states : list[tuple]
+ The list of states to search from.
+ p_memory : list[Pmemory]
+ The list of PMemory objects for parallel search.
+ """
+
+ # batch data
+ data_list = [
+ Data(
+ x=self.mdp.encode_state(mem.state, mem.current_block),
+ edge_index=self.mdp.data.edge_index,
+ y=self.mdp.encode_state(mem.state[:1], mem.current_block),
+ )\
+ for mem in p_memory
+ ]
+
+ batch = Batch.from_data_list(data_list).to(self.mdp.device)
+ policy = model.forward(batch.x, batch.edge_index, batch.y, batch.batch)
+
+ del batch
+ del data_list
+
+ policy = policy.detach().cpu().numpy()
+ # dirichlet random noise
+ policy = (1-self.args['eps']) * policy \
+ + self.args['eps'] \
+ * np.random.dirichlet(
+ [self.args['dirichlet_alpha']]*len(self.mdp.edges),
+ size=policy.shape[0]
+ )
+
+ for i, mem in enumerate(p_memory):
+ p_policy = torch.tensor(policy[i])
+ p_policy = self.mdp.mask_policy(p_policy, states[i])
+
+ mem.root = Node(self.mdp, self.args, states[i], visit_count=1, value_best=1)
+
+ mem.root.expand(p_policy, self.mdp)
+
+ for _ in range(self.args['num_rollouts']):
+ rollout = mem.root.select_random()
+ value = rollout.simulate(self.mdp, mem.current_block)
+ rollout.backpropagate(value)
+
+ for _ in range(self.args['num_searches']):
+ for mem in p_memory:
+ mem.node = None
+ node = mem.root
+ while node.is_fully_expanded():
+ node = node.select()
+
+ value, is_terminal = self.mdp.get_value_and_terminated(
+ node.state,
+ mem.current_block
+ )
+
+ if is_terminal:
+ node.backpropagate(value)
+ else:
+ mem.node = node
+
+ expandable = [i for i in range(len(p_memory)) if p_memory[i].node != None]
+
+ if len(expandable) > 0:
+ chunks = [expandable[i:i+self.args['num_parallel']] for i in range(0, len(expandable), self.args['num_parallel'])]
+ policy = []
+ for chunk in chunks:
+ data_list = [
+ Data(
+ x=self.mdp.encode_state(
+ p_memory[i].node.state,
+ p_memory[i].current_block
+ ),
+ edge_index=self.mdp.data.edge_index,
+ y=self.mdp.encode_state(
+ p_memory[i].node.state[:1],
+ p_memory[i].current_block
+ ),
+ )\
+ for i in chunk
+ ]
+
+ batch = Batch.from_data_list(data_list).to(self.mdp.device)
+ policy_chunk = model.forward(
+ batch.x,
+ batch.edge_index,
+ batch.y,
+ batch.batch,
+ )
+ policy.append(policy_chunk)
+
+ del batch
+ del data_list
+
+ policy = torch.cat(policy, 0)
+
+
+ for i, idx in enumerate(expandable):
+ node = p_memory[idx].node
+ p_policy = self.mdp.mask_policy(policy[i], node.state)
+
+ node.expand(p_policy, self.mdp)
+
+ for _ in range(self.args['num_rollouts']):
+ rollout = node.select_random()
+ value = rollout.simulate(self.mdp, p_memory[idx].current_block)
+ rollout.backpropagate(value)
+
+class PMemory:
+ """
+ A class representing the memory used in parallel
+ MCTS during parallel search.
+
+ Attributes
+ ----------
+ state : list[tuple]
+ The state of the mdp.
+ current_block : int
+ The current block in the mdp.
+ memory : list
+ The memory of the search.
+ root : Node
+ The root node of the search tree.
+ node : Node
+ The current node in the search tree.
+ """
+ def __init__(self, mdp, at_block):
+ self.state = [(mdp.start_node, mdp.start_node, 0)]
+ self.current_block = at_block
+ self.memory = []
+ self.root = None
+ self.node = None
diff --git a/rl_arb/rl_arb/mdp.py b/rl_arb/rl_arb/mdp.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python3
+
+import networkx as nx
+import torch
+import numpy as np
+from torch_geometric.data import Data
+
+from rl_arb.config import DEVICE, WETH
+from rl_arb.logger import logging
+logger = logging.getLogger('rl_circuit')
+
+class MDP:
+ """
+ A class to represent the Markov Decision Process environment.
+
+ Attributes:
+ -----------
+ G : nx.MultiDiGraph
+ The graph representing the network.
+ edges : dict
+ A dictionary of edges with their weights.
+ nodes : list
+ A list of nodes in the graph.
+ edge_list : list
+ A list of edges in the graph.
+ data : Data
+ The data associated with the graph.
+ line_mapping : dict
+ A mapping of lines to their indices.
+ num_blocks : int
+ The number of blocks in the data.
+ current_block : int
+ The current block index.
+ device: torch.device
+ Device object.
+ start_node:
+ Starting node.
+ token_pool_mapping:
+ Dictonary with key=node value=line_mapping value.
+
+ Methods:
+ --------
+ set_current_block(block):
+ Sets the current block index.
+ get_next_state(state, action):
+ Returns the next state given the current state and action.
+ get_valid_actions(state):
+ Returns the valid actions from the current state.
+ check_win(state):
+ Checks if the current state is a terminal state.
+ get_value_and_terminated(state, at_block):
+ Returns the value and whether the state is terminal.
+ encode_state(state, at_block):
+ Encodes the current state into a tensor.
+ mask_policy(policy, state):
+ Masks the policy tensor based on valid actions.
+ """
+
+ def __init__(
+ self,
+ G: nx.MultiDiGraph,
+ data,
+ line_mapping,
+ args,
+ current_block=-1,
+ start_node=0
+ ):
+ """
+ Constructs all the necessary attributes for the NetGame object.
+
+ Parameters:
+ -----------
+ G : nx.MultiDiGraph
+ The graph representing the network.
+ data : torch_geometric.data.Data
+ The data associated with the graph.
+ line_mapping : dict
+ A mapping of lines to their indices.
+ num_blocks : int
+ The number of blocks in the data.
+ current_block : int, optional
+ The current block index (default is -1).
+ start_node: int, optional
+ WETH index label in the problem graph (default is 0).
+ """
+ self.G = G
+ self.args = args
+ self.edges = {(i, j, w['k']): w['weight'] for i, j, w in G.edges(data=True)}
+ self.nodes = list(G.nodes)
+ self.edge_list = list(self.edges.keys())
+ self.action_size = len(self.edge_list)
+
+ self.data = data
+ self.line_mapping = line_mapping
+ self.line_mapping_keys = list(self.line_mapping.keys())
+ self.num_blocks = len(list(self.edges.items())[0][1])-1
+ self.current_block = self.num_blocks
+ self.device = DEVICE
+ self.start_node = start_node
+
+ self.token_pool_mapping = {node: [] for node in self.nodes}
+ for node in self.nodes:
+ for k, v in line_mapping.items():
+ if node == k[0]:
+ self.token_pool_mapping[node].append((v, 0))
+ if node == k[1]:
+ self.token_pool_mapping[node].append((v, 1))
+
+ def set_current_block(self, block_index):
+ """
+ Sets the current block index.
+ Parameters:
+ -----------
+ block : int
+ The current block index.
+ """
+ self.current_block = block_index
+
+ def get_next_state(self, state, action):
+ """
+ Returns the next state given the current state and action.
+
+ Parameters:
+ -----------
+ state : list
+ The current state represented as a list of nodes.
+ action : tuple
+ The action represented as an edge.
+
+ Returns:
+ --------
+ list
+ The next state.
+ """
+ state.append(action)
+ return state
+
+ def get_valid_actions(self, state):
+ """
+ Returns the valid actions from the current state.
+
+ Parameters:
+ -----------
+ state : list
+ The current state represented as a list of nodes.
+
+ Returns:
+ --------
+ list
+ The valid actions represented as edges.
+ """
+ current_node = state[-1][1]
+ filter_edges = [e for e in state[1:]] +\
+ [(e[1], e[0], e[2]) for e in state[1:]]
+
+ valid_actions = [e for e in self.G.edges(current_node, data='k') if e not in filter_edges]
+ return valid_actions
+
+ def check_win(self, state):
+ """
+ Checks if the current state is a terminal state.
+
+ Parameters:
+ -----------
+ state : list
+ The current state represented as a list of nodes.
+
+ Returns:
+ --------
+ bool
+ True if the state is terminal, False otherwise.
+ """
+ return state[0][1] == state[-1][1] and len(state) > 1
+
+ def get_value_and_terminated(self, state, at_block):
+ """
+ Returns the value and whether the state is terminal.
+
+ Parameters:
+ -----------
+ state : list
+ The current state represented as a list of nodes.
+ at_block: int
+ Block of state.
+
+ Returns:
+ --------
+ tuple
+ The value and whether the state is terminal.
+ """
+ valid_actions = self.get_valid_actions(state)
+
+ value = 0
+ terminated = False
+
+ if len(state) >= self.args['cutoff']:
+ terminated = True
+
+ if self.check_win(state):
+ profit = self.calculate_profit(state, at_block)
+ if profit > 3:
+ profit = 1
+
+ value += np.log(profit)*self.args['M']
+
+ terminated = True
+ return value, terminated
+
+ if len(valid_actions) == 0:
+ value = -1
+ terminated = True
+
+ return value, terminated
+
+ def encode_state(self, state, at_block):
+ """
+ Encodes the current state into a tensor.
+
+ Parameters:
+ -----------
+ state : list
+ The current state represented as a list of nodes.
+ at_block: int
+ Block of state.
+
+ Returns:
+ --------
+ torch.Tensor
+ The encoded state tensor.
+ """
+ e_x = self.data.x.detach().clone()
+ e_x= torch.dstack([
+ e_x[:, at_block], # exchange rate
+ e_x[:, self.num_blocks+1], # used binary
+ e_x[:, self.num_blocks+2], # t0 binary
+ e_x[:, self.num_blocks+3], # t1 binary
+ ]).squeeze(0)
+
+ # encode the current t0 or t1
+ line_mapping_keys = list(self.line_mapping.keys())
+ start_node = state[0][1]
+ current_node= state[-1][1]
+
+ for p, t01 in self.token_pool_mapping[start_node]:
+ e_x[p, t01+2] = 1
+
+ if len(state) > 1:
+ profit = np.log(self.calculate_profit(state, at_block))
+ if state[-1] in line_mapping_keys:
+ line_state_current = state[-1]
+ else:
+ line_state_current = (state[-1][1], state[-1][0], state[-1][2])
+
+ e_x[self.line_mapping[line_state_current],
+ line_state_current[:2].index(current_node)+2] = profit
+
+ # encode the used edges
+ edge_idx = []
+ for e in state[1:]:
+ if e in line_mapping_keys:
+ edge_idx.append(self.line_mapping[e])
+ if (e[1], e[0], e[2]) in line_mapping_keys:
+ edge_idx.append(self.line_mapping[(e[1], e[0], e[2])])
+ e_x[edge_idx, 1] = 1
+
+ return e_x.to(self.device)
+
+ def mask_policy(self, policy, state):
+ """
+ Masks the policy tensor based on valid actions.
+
+ Parameters:
+ -----------
+ policy : torch.Tensor
+ The policy tensor.
+ state : list
+ The current state represented as a list of nodes.
+
+ Returns:
+ --------
+ torch.Tensor
+ The masked policy tensor.
+ """
+ valid_actions = self.get_valid_actions(state)
+ valid_indices = torch.tensor(
+ [self.edge_list.index(e) for e in valid_actions]
+ )
+
+ mask = torch.zeros_like(policy)
+ mask[valid_indices] = 1
+
+ masked_policy = policy * mask
+ return masked_policy / masked_policy.sum()
+
+ def calculate_profit(self, state, at_block):
+ return np.prod(
+ [self.edges[edge][at_block] for edge in state[1:]]
+ )
diff --git a/rl_arb/rl_arb/net.py b/rl_arb/rl_arb/net.py
@@ -0,0 +1,185 @@
+#!/usr/bin/env python3
+
+import torch
+from torch import nn
+from torch.nn import functional as F
+from torch_geometric.nn import BatchNorm, GATConv, LayerNorm, Sequential, Linear
+
+import logging
+
+from torch_geometric.nn.glob import global_mean_pool
+logger = logging.getLogger('rl_circuit')
+
+from rl_arb.config import DEVICE
+
+class PolicyNet(nn.Module):
+ """
+ A Residual Network model for graph-based data.
+
+ Parameters
+ ----------
+ args : dict
+ in_channels : int
+ Number of input channels.
+ emb_channels : int
+ Number of embedding channels.
+ num_heads : int, optional
+ Number of attention heads. Default is 4.
+ num_layers : int, optional
+ Number of layers in the network. Default is 3.
+ policy_mheads : int
+ Number of attention heads for the policy head.
+ value_mheads : int
+ Number of attention heads for the value head.
+
+ Attributes
+ ----------
+ encoder : Linear
+ Linear layer for encoding input features.
+ hidden_blocks : nn.ModuleList
+ List of hidden residual blocks.
+ policy_head : Sequential
+ Policy head for outputting policy logits.
+ """
+ def __init__(
+ self,
+ args,
+ ):
+ super().__init__()
+
+ self.encoder = Linear(args['in_channels'], args['emb_channels'])
+
+ self.hidden_blocks = nn.ModuleList()
+ for _ in range(args['num_layers']):
+ self.hidden_blocks.append(
+ ResCovBlock(args)
+ )
+
+ self.policy_head = Sequential('x, edge_index, batch', [
+ (GATConv(
+ 3*args['emb_channels'],
+ args['emb_channels'],
+ heads=args['policy_mheads'],
+ ), 'x, edge_index -> x'),
+ (LayerNorm(args['emb_channels']*args['policy_mheads']), 'x, batch -> x'),
+ nn.ReLU(),
+ (Linear(args['emb_channels']*args['policy_mheads'], 2, bias=False), 'x -> x'),
+ ])
+
+ def forward(self, node_attr, edge_index, y, batch=None):
+ """
+ Forward pass of the ResNet model.
+
+ Parameters
+ ----------
+ node_attr : Tensor
+ Node attributes.
+ edge_index : Tensor
+ Edge indices.
+
+ Returns
+ -------
+ tuple
+ A tuple containing the value estimation and policy logits.
+ """
+ x = self.encoder(node_attr)
+ x_1 = self.encoder(y)
+ for block in self.hidden_blocks:
+ x = block(x, edge_index, batch)
+
+ mean = global_mean_pool(x, batch)
+ if batch != None:
+ x_n = []
+ for i in batch.unique():
+ count = (batch == i).sum()
+ x_n.append(mean[i].repeat(count, 1))
+ x_n = torch.cat(x_n, 0)
+ else:
+ x_n = mean.repeat(x.shape[0], 1)
+ x_c = torch.cat((x, x_n, x_1), dim=1)
+
+ policy = self.policy_head(x_c, edge_index, batch).flatten().unsqueeze(0)
+
+ if batch != None:
+ policy = policy.view(len(batch.unique()), -1)
+
+ policy = torch.softmax(policy, dim=1)
+
+ return policy
+
+class ResCovBlock(nn.Module):
+ """
+ A Residual Block used in the ResNet model.
+
+ Parameters
+ ----------
+ args : dict
+ in_channels : int
+ Number of input channels.
+ emb_channels : int
+ Number of embedding channels.
+ num_heads : int
+ Number of attention heads.
+ ff_dim : int
+ Dimension of the feed-forward network.
+
+ Attributes
+ ----------
+ in_layer : Sequential
+ Initial layer with GATConv, BatchNorm, and Linear layers.
+ feed_forward : nn.Sequential
+ Feed-forward network with Linear and ReLU layers.
+ batch_norm1 : BatchNorm
+ Batch normalization layer after the initial layer.
+ batch_norm2 : BatchNorm
+ Batch normalization layer after the feed-forward network.
+ """
+ def __init__(self, args):
+ super().__init__()
+ self.in_layer = Sequential('x, edge_index', [
+ (GATConv(
+ args['emb_channels'],
+ args['emb_channels'],
+ heads=args['num_heads']
+ ), 'x, edge_index -> x'),
+ Linear(
+ args['emb_channels']*args['num_heads'],
+ args['emb_channels'],
+ bias=False
+ )
+ ])
+
+ self.batch_norm1 = LayerNorm(args['emb_channels'])
+
+ self.feed_forward = nn.Sequential(
+ Linear(args['emb_channels'], args['ff_dim']),
+ nn.ReLU(),
+ Linear(args['ff_dim'], args['emb_channels'])
+ )
+ self.batch_norm2 = LayerNorm(args['emb_channels'])
+
+ def forward(self, node_attr, edge_index, batch=None):
+ """
+ Forward pass of the ResBlock.
+
+ Parameters
+ ----------
+ node_attr : Tensor
+ Node embeddings.
+ edge_index : Tensor
+ Edge indices.
+
+ Returns
+ -------
+ Tensor
+ Output tensor after applying the residual block.
+ """
+ res = node_attr
+ x = self.in_layer(node_attr, edge_index)
+ x = self.batch_norm1(res + x, batch)
+
+ res = x
+ x = self.feed_forward(x)
+ x = self.batch_norm2(res + x, batch)
+
+ return x
diff --git a/rl_arb/rl_arb/reinforce.py b/rl_arb/rl_arb/reinforce.py
@@ -0,0 +1,179 @@
+#!/usr/bin/env python3
+
+import torch
+import numpy as np
+import pickle
+from torch_geometric.data import Data, Batch
+
+from rl_arb.config import DEVICE
+
+import logging
+logger = logging.getLogger('rl_circuit')
+
+class Reinforce:
+ def __init__(self, model, optimizer, mdp, mcts, args):
+ self.model = model
+ self.mdp = mdp
+ self.mcts = mcts
+ self.optimizer = optimizer
+ self.args = args
+
+ @torch.no_grad()
+ def play(self, state, at_block, mode="learn"):
+ mcts_probs = []
+ actions, rewards, transitions, g_values =[], [], [], []
+ suc_states = 0
+ while True:
+ probs = self.model(
+ self.mdp.encode_state(state, at_block),
+ self.mdp.data.edge_index,
+ self.mdp.encode_state(state[:1], at_block)
+ )
+
+ probs = probs.squeeze(0).detach().cpu()
+
+ probs = self.mdp.mask_policy(probs, state).numpy()
+
+ if mode == "test":
+ action_idx = np.argmax(probs)
+ else:
+ action_idx = np.random.choice(list(
+ range(len(self.mdp.edge_list))
+ ), p=probs/probs.sum())
+
+ action = self.mdp.edge_list[action_idx]
+
+ actions.append(action)
+ mcts_probs.append(probs)
+
+ state = self.mdp.get_next_state(state, action)
+
+ value, is_terminal = self.mdp.get_value_and_terminated(
+ state,
+ at_block
+ )
+
+ if is_terminal:
+ rewards.append(value)
+
+ if value != -1:
+ suc_states +=1
+
+ for i in range(1, len(state)):
+ transitions.append((at_block, state[:i]))
+ g_values.append(
+ self.args['gamma']**(
+ len(state)-len(state[:i])
+ )*value
+ )
+ break
+ return mcts_probs, actions, rewards, transitions, g_values, suc_states
+
+ def learn(self):
+ self.model.to(DEVICE)
+
+ mcts_probs = []
+ rewards = []
+ g_values = []
+ actions = []
+ transitions = []
+ suc_states = 0
+ max_reward = 0
+ mean_rewards = 0
+
+ suc_states_hist = []
+ mean_rewards_hist = []
+ test_rewards_hist = []
+
+ for itr in range(self.args['num_reinforce']):
+ if itr % 2 == 0:
+ at_block = 10
+ else:
+ at_block = 11
+ self.mdp.current_block = at_block
+ self.mcts.mdp.current_block = at_block
+
+ state = [(self.mdp.start_node, self.mdp.start_node, 0)]
+ m, a, r, t, g, suc = self.play(state, at_block)
+ suc_states += suc
+ transitions += t
+ g_values += g
+ rewards += r
+ actions += a
+ mcts_probs += m
+
+ if itr%300==0 and itr > 0:
+ state = [(self.mdp.start_node, self.mdp.start_node, 0)]
+ _, _, tr, _, _, _ = self.play(state, at_block, mode="test")
+ logger.info(f"mean rewards: {mean_rewards}")
+ logger.info(f"max rewards: {max_reward}")
+ logger.info(f"test rewards: {tr}")
+ logger.info(f"suc_states : {suc_states}")
+
+ mean_rewards = np.mean(rewards)
+
+ if max_reward < np.max(rewards):
+ max_reward = np.max(rewards)
+
+ suc_states_hist.append(suc_states)
+ mean_rewards_hist.append(mean_rewards)
+ test_rewards_hist.append(tr)
+
+ rewards = []
+ suc_states = 0
+
+
+ data_list = [
+ Data(
+ x=self.mdp.encode_state(s, b),
+ edge_index=self.mdp.data.edge_index,
+ y=self.mdp.encode_state(s[:1], b),
+ )\
+ for b, s in transitions
+ ]
+ batch = Batch.from_data_list(data_list).to(DEVICE)
+
+ policy_outs = self.model.forward(
+ batch.x,
+ batch.edge_index,
+ batch.y,
+ batch.batch,
+ )
+
+ del batch
+ del data_list
+
+ one_hot = torch.zeros_like(policy_outs).to(DEVICE)
+ for a, p in zip(actions, one_hot):
+ p[self.mdp.edge_list.index(a)] = 1
+
+ g_tensor = torch.tensor(g_values).to(DEVICE)
+
+ ce = torch.mean(torch.log(policy_outs) * one_hot, dim=1)
+ loss = -torch.mean(ce * (g_tensor-mean_rewards))
+
+ self.optimizer.zero_grad()
+ loss.backward()
+ self.optimizer.step()
+
+ message = f"""
+ ITR: {itr}
+ Mean CE Loss: {torch.mean(ce)}
+ REINFORCE Loss: {loss}
+ """
+ logger.info(message)
+
+ mcts_probs = []
+ g_values = []
+ actions = []
+ transitions = []
+
+ with open(f"./reinforce.pickle", "wb") as f:
+ pickle.dump(
+ [
+ suc_states_hist,
+ mean_rewards_hist,
+ test_rewards_hist
+ ],
+ f
+ )
diff --git a/rl_arb/rl_arb/rlearn.py b/rl_arb/rl_arb/rlearn.py
@@ -0,0 +1,558 @@
+#!/usr/bin/env python3
+
+import numpy as np
+import torch
+from torch.nn import functional as F
+from torch_geometric.data import Batch, Data
+import torch.multiprocessing as mp
+from tqdm.contrib.telegram import tqdm
+from torch.nn.parallel import DistributedDataParallel as DDP
+from torch import distributed as dist
+import os
+import pickle
+
+import logging
+logger = logging.getLogger('rl_circuit')
+
+from rl_arb.net import PolicyNet
+from rl_arb.mcts import MCTSParallel, PMemory
+from rl_arb.config import (
+ DEVICE,
+ TELEGRAM_TOKEN,
+ TELEGRAM_CHAT_ID,
+ ARGS_MODEL,
+)
+from rl_arb.utils import (
+ update_me,
+ send_telegram_message,
+)
+
+
+class AgentRLearn():
+ def __init__(self, model, mdp, args):
+ """
+ RL algorithm class for training and self-play using Monte Carlo Tree
+ Search (MCTS).
+
+ Parameters
+ ----------
+ model : torch.nn.Module
+ The neural network model used for policy and value prediction.
+ mdp : MDP
+ The MDP environment that provides the logic and state transitions.
+ args : dict
+ A dictionary of arguments and hyperparameters for training and self-play.
+
+ Methods
+ -------
+ self_play()
+ Executes self-play to generate training data.
+ train(memory)
+ Trains the model using the training data from self_play.
+ track_baseline(values, gamma_factors, blocks)
+ Updates the baseline_tracker and give baseline values for current iteration.
+ """
+ self.model = model
+ self.args = args
+ self.mcts = MCTSParallel(mdp, self.args)
+
+ self.pbar_play = False
+ self.values = []
+ self.baseline_tracker = {}
+ self.avg_itr_value = [0.0]
+ self.test_values= []
+
+ def self_play(self):
+ """
+ Executes self-play to generate training data.
+
+ Returns
+ -------
+ list
+ A list of tuples containing mdp states, policy probabilities,
+ value estimates, and block indices.
+ """
+ return_mem = []
+ at_block = np.random.choice(self.mcts.mdp.num_blocks)
+ self.mcts.mdp.current_block = at_block
+ p_memory = [PMemory(self.mcts.mdp, np.random.choice(self.mcts.mdp.num_blocks)) for _ in range(self.args['num_parallel'])]
+
+ if self.pbar_play:
+ pbar = tqdm(
+ total=self.args['num_parallel'],
+ desc='self_play',
+ token=str(TELEGRAM_TOKEN),
+ chat_id=str(TELEGRAM_CHAT_ID),
+ disable=not self.args['telegram']
+ )
+
+ while len(p_memory) > 0:
+
+ states = [mem.state for mem in p_memory]
+ if self.pbar_play:
+ pbar.set_description(f"self_play state_len {len(states[0])}")
+
+ self.mcts.search(self.model, states, p_memory)
+
+ for i in range(len(p_memory))[::-1]:
+ mem = p_memory[i]
+ probs = np.zeros(len(self.mcts.mdp.edge_list))
+
+ for child in mem.root.children:
+ probs[
+ self.mcts.mdp.edge_list.index(child.action_taken)
+ ] = child.value_best
+
+ if np.sum(probs) == 0:
+ for child in mem.root.children:
+ probs[
+ self.mcts.mdp.edge_list.index(child.action_taken)
+ ] = child.visit_count
+
+ probs /= np.sum(probs)
+
+ mem.memory.append((
+ mem.state.copy(),
+ probs,
+ mem.current_block
+ ))
+
+ action = self.mcts.mdp.edge_list[np.random.choice(len(probs), p=probs)]
+ mem.state = self.mcts.mdp.get_next_state(mem.state, action)
+
+ value, is_terminal = self.mcts.mdp.get_value_and_terminated(
+ mem.state,
+ mem.current_block
+ )
+
+ if is_terminal:
+ if self.pbar_play:
+ pbar.update(1)
+ for hist_state, hist_probs, current_block in mem.memory:
+ return_mem.append((
+ hist_state,
+ hist_probs,
+ value,
+ self.args['gamma']**(len(mem.state)-(len(hist_state)+1)),
+ action,
+ current_block,
+ ))
+ del p_memory[i]
+
+ return return_mem
+
+ def track_baseline(self, values, gamma_factors, blocks):
+ bs = np.array(blocks)
+ vs = np.array(values)
+ baseline = np.zeros_like(bs, dtype=float)
+
+ for block in np.unique(bs):
+ idxs = np.where(bs==block)[0]
+ mean_val = np.mean(vs[idxs])
+
+ if block not in self.baseline_tracker:
+ self.baseline_tracker[block] = mean_val
+ else:
+ self.baseline_tracker[block] = 1/2 * (mean_val + self.baseline_tracker[block])
+
+ baseline[idxs] = self.baseline_tracker[block]
+
+
+ return baseline*np.array(gamma_factors)
+
+
+ def learn(self, optimizer):
+ """
+ Main loop for the learning process, including self-play and training.
+ """
+ torch.save(self.model.state_dict(), "./model/model_0.pt")
+ torch.save(optimizer.state_dict(), "./model/optimizer_0.pt")
+ for iteration in range(self.args['num_iterations']):
+ logger.info(f"Iterations: {iteration+1}/{self.args['num_iterations']}")
+ if self.args['telegram']:
+ send_telegram_message(f"Iterations: {iteration+1}/{self.args['num_iterations']}")
+
+ memory = []
+ self.model.eval()
+ if not self.args['multicore']:
+ # single core self-play
+ self.model.to(DEVICE)
+ self.mcts.mdp.device = DEVICE
+ for play_iter in tqdm(
+ range(self.args['num_self_play_iterations']//self.args['num_parallel']),
+ desc="self play",
+ token=str(TELEGRAM_TOKEN),
+ chat_id=str(TELEGRAM_CHAT_ID),
+ disable=not self.args['telegram'],
+ ):
+ memory += self.self_play()
+
+ else:
+ cpu = torch.device('cpu')
+ self.model.to(cpu)
+ self.model.load_state_dict(
+ torch.load(
+ f"./model/model_{iteration}.pt",
+ weights_only = True,
+ map_location=cpu
+ )
+ )
+ self.model.share_memory()
+ self.mcts.mdp.device = cpu
+ # multicore self-play
+ play_iter = self.args['num_self_play_iterations']
+ num_parallel = self.args['num_parallel']
+ num_processes = self.args['num_processes']
+ per_processor = play_iter//num_parallel//num_processes
+ # at each self play choose prices from a different block number
+ with mp.Pool(processes=num_processes) as pool:
+ results = pool.starmap(
+ self_play_num_times,
+ [(self, per_processor, 'cuda:0', True)] +\
+ [(self, per_processor, 'cuda:0', False) for _ in range(num_processes//2 - 1)] +\
+ [(self, per_processor, 'cuda:1', False) for _ in range(num_processes//2 - 1)] +\
+ [(self, per_processor, 'cuda:1', True)]
+ )
+ pool.terminate()
+
+ for result in results:
+ memory += result
+
+ states, _, values, gamma_factors, _, blocks = zip(*memory)
+ baseline = self.track_baseline(values, gamma_factors, blocks)
+
+ memory = [(*it, float(baseline[i])) for i, it in enumerate(memory)]
+
+ terminal_idxs = np.where(np.array(gamma_factors)==1)[0]
+ terminal_values = np.array(values)[terminal_idxs]
+ terminal_states = np.array([len(s)+1 for s in states])[terminal_idxs]
+
+ self.values.append((np.mean(terminal_values), np.mean(terminal_states)))
+ if self.args['telegram']:
+ send_telegram_message(f"""
+ Average values {np.mean(terminal_values)}
+ Average state_len {np.mean(terminal_states)}
+ """)
+
+ world_size = torch.cuda.device_count()
+ mp.spawn(
+ train,
+ args=(world_size, memory, self.mcts, iteration),
+ nprocs=world_size,
+ join=True
+ )
+
+ with open(f"./baseline/baseline_{iteration}.pickle", "wb") as f:
+ pickle.dump(self.baseline_tracker, f)
+
+ with open("values.pickle", "wb") as f:
+ pickle.dump(self.values, f)
+
+ test_values = self.test_model(iteration)
+ self.test_values.append(test_values)
+ average_value = float(np.mean(test_values))
+ with open("test_values.pickle", "wb") as f:
+ pickle.dump(self.test_values, f)
+
+ send_telegram_message(f"Average profit {average_value}")
+
+ if self.args['telegram']:
+ send_telegram_message("DONE!")
+
+
+ def test_model(self, iteration):
+ cpu = torch.device('cpu')
+ self.model.to(cpu)
+ self.model.load_state_dict(
+ torch.load(
+ f"./model/model_{iteration+1}.pt",
+ weights_only = True,
+ map_location=cpu
+ )
+ )
+ self.model.share_memory()
+ self.mcts.mdp.device = cpu
+ # multicore self-play
+ play_iter = self.args['num_self_play_iterations']
+ num_parallel = self.args['num_parallel']
+ num_processes = play_iter//num_parallel
+ per_processor = play_iter//num_parallel//num_processes
+
+ with mp.Pool(processes=num_processes) as pool:
+ results = pool.starmap(
+ test_blocks,
+ [(self, per_processor, 'cuda:0', True)] +\
+ [(self, per_processor, 'cuda:0', False) for _ in range(num_processes//2 - 1)] +\
+ [(self, per_processor, 'cuda:1', False) for _ in range(num_processes//2 - 1)] +\
+ [(self, per_processor, 'cuda:1', True)]
+ )
+ pool.terminate()
+
+ values =[]
+ for result in results:
+ values += result
+
+ return values
+
+ def test_play(self):
+ """
+ Executes self-play to generate training data.
+
+ Returns
+ -------
+ list
+ A list of tuples containing mdp states, policy probabilities,
+ value estimates, and block indices.
+ """
+ return_mem = []
+ p_memory = [PMemory(self.mcts.mdp, np.random.choice(self.mcts.mdp.num_blocks)) for _ in range(self.args['num_parallel'])]
+
+ if self.pbar_play:
+ pbar = tqdm(
+ total=self.args['num_parallel'],
+ desc='test_play',
+ token=str(TELEGRAM_TOKEN),
+ chat_id=str(TELEGRAM_CHAT_ID),
+ disable=not self.args['telegram']
+ )
+
+ while len(p_memory) > 0:
+ states = [mem.state for mem in p_memory]
+
+ if self.pbar_play:
+ pbar.set_description(f"test_play state_len {len(states[0])}")
+
+ self.mcts.search(self.model, states, p_memory)
+
+ for i in range(len(p_memory))[::-1]:
+ mem = p_memory[i]
+ probs = np.zeros(len(self.mcts.mdp.edge_list))
+ for child in mem.root.children:
+ probs[
+ self.mcts.mdp.edge_list.index(child.action_taken)
+ ] = child.value_best
+
+ if np.sum(probs) == 0:
+ for child in mem.root.children:
+ probs[
+ self.mcts.mdp.edge_list.index(child.action_taken)
+ ] = child.visit_count
+
+ probs /= np.sum(probs)
+
+ action = self.mcts.mdp.edge_list[np.argmax(probs)]
+ mem.state = self.mcts.mdp.get_next_state(mem.state, action)
+
+ value, is_terminal = self.mcts.mdp.get_value_and_terminated(
+ mem.state,
+ mem.current_block
+ )
+
+ if is_terminal:
+ if self.pbar_play:
+ pbar.update(1)
+ return_mem.append(value)
+ del p_memory[i]
+
+ return return_mem
+
+ def override_update(self, iteration):
+
+ self.model.load_state_dict(
+ torch.load(
+ f"./model/model_{iteration}.pt",
+ weights_only = True,
+ )
+ )
+ optimizer = torch.optim.Adam(self.model.parameters(), lr=0.01)
+ optimizer.load_state_dict(
+ torch.load(
+ f"./model/optimizer_{iteration}.pt",
+ weights_only = True,
+ )
+ )
+
+ torch.save(self.model.state_dict(), f"./model/model_{iteration+1}.pt")
+ torch.save(optimizer.state_dict(), f"./model/optimizer_{iteration+1}.pt")
+
+
+def test_blocks(rlearn, times=100, device='cpu', pbar=False):
+ """
+ Helper function to perform self-play multiple times while testing.
+
+ Parameters
+ ----------
+ rlearn: AgentRLearn
+ The AgentRLearn instance to use for self-play.
+ times : int, optional
+ The number of self-play iterations to perform (default is 100).
+ device: string, optional
+ Device to execute the code.
+ pbar: bool, optional
+ Print a tqdm bar or not.
+
+ Returns
+ -------
+ list
+ A list of tuples containing the rewards.
+ """
+ values = []
+
+ rlearn.pbar_play = pbar
+ rlearn.model.to(device)
+ rlearn.mcts.mdp.device = device
+ for i in range(times):
+ values += rlearn.test_play()
+ return values
+
+
+def self_play_num_times(rlearn, times=100, device='cpu', pbar=False):
+ """
+ Helper function to perform self-play multiple times.
+
+ Parameters
+ ----------
+ rlearn: AgentRLearn
+ The AgentRLearn instance to use for self-play.
+ times : int, optional
+ The number of self-play iterations to perform (default is 100).
+ device: string, optional
+ Device to execute the code.
+ pbar: bool, optional
+ Print a tqdm bar or not.
+
+ Returns
+ -------
+ list
+ A list of tuples containing mdp states, policy probabilities, value estimates, and block indices.
+ """
+ memory = []
+
+ rlearn.pbar_play = pbar
+ rlearn.model.to(device)
+ rlearn.mcts.mdp.device = device
+ for _ in range(times):
+ memory += rlearn.self_play()
+ return memory
+
+
+def train(rank, world_size, memory, mcts, iteration):
+ """
+ Distributed multi gpu training in pytorch.
+
+ Parameters
+ ----------
+ world_size: int
+ Number of cpus
+ memory : list
+ A list of containing mdp states, policy probabilities,
+ value estimates, and block indices.
+ mcts: MCTSParallel
+ Monte Carlo Tree search object.
+ iteration: int
+ Current search iteration
+ """
+ os.environ['MASTER_ADDR'] = 'localhost'
+ os.environ['MASTER_PORT'] = '12355'
+ dist.init_process_group('nccl', rank=rank, world_size=world_size)
+
+ dist.barrier()
+ model = PolicyNet(ARGS_MODEL)
+ model.load_state_dict(
+ torch.load(
+ f"./model/model_{iteration}.pt",
+ weights_only = True,
+ )
+ )
+ model.to(rank)
+ optimizer = torch.optim.Adam(model.parameters(), lr=0.01)
+ optimizer.load_state_dict(
+ torch.load(
+ f"./model/optimizer_{iteration}.pt",
+ weights_only = True,
+ )
+ )
+
+ model = DDP(model, device_ids=[rank])
+
+ lm = len(memory)
+ memory = [memory[i: i+lm//world_size] for i in range(0, lm, lm//world_size)][rank]
+
+ epoch_loss = []
+ avg_state_len = []
+ avg_rewards = []
+ for epoch_iter in tqdm(
+ range(mcts.args['num_epochs']),
+ desc="epochs",
+ token=str(TELEGRAM_TOKEN),
+ chat_id=str(TELEGRAM_CHAT_ID),
+ disable=(not mcts.args['telegram'] and rank != 0),
+ ):
+ np.random.shuffle(memory)
+ for batch_idx in range(0, len(memory), mcts.args['batch_size']):
+ sample = memory[batch_idx:np.min([len(memory) - 1, batch_idx + mcts.args['batch_size']])]
+ try:
+ states , policy_targets, values, gamma_factors, actions, block_indices, baseline = zip(*sample)
+ except ValueError: # batch is len 0.
+ print(f"batch_idx {batch_idx}")
+ print(f"From-TO : {[batch_idx, np.min([len(memory) - 1, batch_idx + mcts.args['batch_size']])]}")
+ print(f"len memory {len(memory)}")
+ print(f"len sample {len(sample)}")
+ continue
+
+ policy_targets, value_targets = np.array(policy_targets), np.array(values)*np.array(gamma_factors)
+ policy_targets = torch.tensor(policy_targets).to(rank).float()
+ value_targets = torch.tensor(value_targets).to(rank).float()
+ baseline = torch.tensor(baseline).to(rank)
+
+ data_list = [
+ Data(
+ x=mcts.mdp.encode_state(s, b),
+ edge_index=mcts.mdp.data.edge_index,
+ y=mcts.mdp.encode_state(s[:1], b),
+ )\
+ for s, b in zip(states, block_indices)
+ ]
+ batch = Batch.from_data_list(data_list).to(rank)
+
+ policy_outs = model.forward(
+ batch.x,
+ batch.edge_index,
+ batch.y,
+ batch.batch,
+ )
+
+ del batch
+ del data_list
+
+ one_hot = torch.zeros_like(policy_outs).to(rank)
+ for a, p in zip(actions, one_hot):
+ p[mcts.mdp.edge_list.index(a)] = 1
+
+ cross_entropy = torch.sum(torch.log(policy_outs) * one_hot, dim=1)
+ loss = -torch.mean(cross_entropy * (value_targets - baseline))
+
+ epoch_loss.append(loss.item())
+ avg_rewards.append(value_targets.mean().item())
+ avg_state_len.append(
+ np.mean(np.array([len(s) for s in states]))
+ )
+
+ optimizer.zero_grad()
+ loss.backward()
+ optimizer.step()
+
+ if mcts.args['telegram'] and rank == 0:
+ update_me(
+ np.mean(epoch_loss),
+ np.mean(avg_state_len),
+ np.mean(avg_rewards),
+ epoch_iter,
+ iteration,
+ )
+
+ if rank == 0:
+ torch.save(model.module.state_dict(), f"./model/model_{iteration+1}.pt")
+ torch.save(optimizer.state_dict(), f"./model/optimizer_{iteration+1}.pt")
+
+ dist.destroy_process_group()
diff --git a/rl_arb/rl_arb/utils.py b/rl_arb/rl_arb/utils.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python3
+
+import pandas as pd
+import networkx as nx
+import numpy as np
+import torch
+import os
+import pickle
+import requests
+from torch_geometric.nn import summary
+
+from rl_arb.config import TELEGRAM_CHAT_ID, TELEGRAM_SEND_URL, DEVICE
+from rl_arb.logger import logging
+logger = logging.getLogger('rl_circuit')
+
+def load_pools_and_tokens(path_pools, path_tokens):
+ """
+ Load pool & token data into pd.DataFrame/s from csv files.
+
+ Parameters
+ ----------
+ path_pools : str
+ File path to csv file for the pools
+ path_tokens : str
+ File path to csv file for the tokens
+
+ Returns
+ -------
+ tuple
+ A tuple containing:
+ - pools (pd.DataFrame): pool data.
+ - tokens (pd.DataFrame): token data.
+ """
+ pools = pd.read_csv(
+ path_pools,
+ names = ['index', 'address', 'version', 'token0', 'token1', 'fee', 'block_number', 'time_stamp', 'tick_spacing'],
+ header = None,
+ ).sort_index().drop_duplicates()
+ tokens = pd.read_csv(
+ path_tokens,
+ header = None,
+ names = ['index', 'address', 'name', 'symbol', 'decimals']
+ ).sort_index().drop_duplicates()
+
+
+ return pools, tokens
+
+
+def make_price(price):
+ """
+ Calculate the price from price pd.DataFrame
+ Uniswapv3 price is t1/t0 -> sqrt_price_x96 = sqrt(reserve1/reserve0) * 2**96
+ Uniswapv2 price we will define also as t1/t0
+
+ Parameters
+ ----------
+ price : pd.DataFrame
+
+ Returns
+ -------
+ list: prices.
+ """
+ block_price = []
+ for _, p in price.iterrows():
+ if (spx96 := p['sqrt_price_x96']) != None:
+ block_price.append((int(spx96) / 2**96)**2)
+ else:
+ t0 = int(p['reserve_t0'])
+ t1 = int(p['reserve_t1'])
+ if t0 == 0:
+ price = 0
+ else:
+ price = t1/t0
+ block_price.append(price)
+ return block_price
+
+
+def pools_to_edge_list(pools, prices):
+ """
+ Makes an edge list from pools & prices for the problem graph.
+
+ Parameters
+ ----------
+ pools : pd.DataFrame
+ pool data
+ prices : pd.DataFrame
+ price data
+
+ Returns
+ -------
+ list
+ List of edges in the form of a tuple (token0, token1, attributes).
+ Where the attributes is a type dict with keys:
+ - 'k' (int): Repeated count of the pool with the same tokens.
+ - 'weight' (list): The historical prices of the specific pool.
+ """
+ edge_list = []
+ cache = []
+ for (_, pool) in pools.iterrows():
+
+ t0 = pool['token0']
+ t1 = pool['token1']
+ p = make_price(prices[prices['pool_address'] == pool['address']])
+
+
+ k = 0
+ for e in cache:
+ if (t0, t1) == e or (t1, t0) == e:
+ k += 1
+ edge_list.append(
+ (t0, t1,
+ {'k': k, 'weight': p, 'address': pool['address'], 'fee': int(pool['fee'])/1e6})
+ )
+ cache.append((t0, t1))
+
+ return edge_list
+
+
+def make_token_graph(pools, prices):
+ """
+ Make a directed multi graph from pool and price data.
+
+ Parameters
+ ----------
+ pools : pd.DataFrame
+ Pool data.
+ prices : pd.DataFrame
+ Price data.
+
+ Returns
+ -------
+ nx.MultiDiGraph
+ A directed multi graph. Nodes represent tokens
+ edges represent pools with attributes generated
+ by the function pools_to_edge_list
+ """
+ edge_list = pools_to_edge_list(pools, prices)
+
+ G = nx.MultiDiGraph()
+ G.add_edges_from(edge_list)
+ return G
+
+def linear_node_relabel(G):
+ """
+ Relabel the nodes linearly. Input node labels
+ are ETH addresses which are relabeled in a chronological order
+ to integer values starting from 0.
+
+ Parameters
+ ----------
+ G : nx.MultiDiGraph
+ Input graph.
+
+ Returns
+ -------
+ tuple
+ A tuple containing:
+ - G (nx.MultiDiGraph): Graph with relabeled nodes (automatically edges).
+ - mapping (dict): Mapping dictionary.
+ """
+ mapping = {}
+ inv_mapping = {}
+ nodes = list(G.nodes())
+ for i, node in enumerate(nodes):
+ mapping[node] = i
+ inv_mapping[i] = node
+ G = nx.relabel_nodes(G, mapping)
+ return G, mapping
+
+
+def filter_pools_with_no_gradient(pools, prices):
+ """
+ Filter out pools that have no change in price by computing the gradient of
+ the historical prices.
+
+ Parameters
+ ----------
+ pools : pd.DataFrame
+ Pool data.
+ prices : pd.DataFrame
+ Price data
+
+ Returns
+ -------
+ tuple
+ A tuple containing:
+ - filtered_pools (pd.DataFrame): Filtered pool data.
+ - filtered_prices (pd.DataFrame): Filtered price data.
+ """
+ pools = pools[pools['address'].isin(set(prices['pool_address']))]
+ ticks = len(prices['block_number'].unique())
+ mask = []
+ for _, pool in pools.iterrows():
+ t0 = pool['token0']
+ t1 = pool['token1']
+ p = make_price(prices[prices['pool_address'] == pool['address']])
+
+ pbn = len(list(
+ prices[prices['pool_address'] == pool['address']]['block_number']
+ ))
+ if pbn != ticks or np.count_nonzero(np.gradient(p)) < ticks*2//3:
+ mask.append(False)
+ else:
+ mask.append(True)
+
+
+ pools = pools[mask]
+ prices = prices[prices['pool_address'].isin(list(pools['address']))]
+ return pools, prices
+
+def save_loss(
+ loss,
+ avg_state_len
+):
+ if not os.path.exists('./model/loss'):
+ os.mkdir('./model/loss')
+
+ with open(f'./model/loss/loss.pickle', "wb") as f:
+ pickle.dump(loss, f)
+
+ with open(f'./model/loss/avg_state_len.pickle', "wb") as f:
+ pickle.dump(avg_state_len, f)
+
+
+def update_me(
+ loss,
+ avg_state_len,
+ avg_rewards,
+ epoch_iter,
+ iteration,
+):
+ """
+ Sends a message through a telegram bot on current training progress
+
+ Parameters
+ ----------
+ loss: float
+ Loss function mean over the epoch.
+ avg_state_len: float
+ Average length of the states.
+ avg_rewards: float
+ Average rewards of the states.
+ epoch_ter: int
+ Current epoch.
+ iteration: int
+ Current iteration.
+ """
+
+ message = f"""
+ ITR: {iteration+1} | EPOCH: {epoch_iter}
+ REINFORCE Loss: {loss}
+ Average state length: {avg_state_len}
+ Average rewards: {avg_rewards}
+ """
+ send_telegram_message(message)
+
+
+def send_telegram_message(message):
+ """
+ Send a message through a telegram-bot.
+
+ Parameters
+ ----------
+ message: str
+ String containing the message to be sent by the bot.
+ """
+ requests.post(TELEGRAM_SEND_URL, json={'chat_id': TELEGRAM_CHAT_ID, 'text': message})
+
+
+
+def print_summary(problem):
+ """
+ Prints torchsummary.summary of the model.
+
+ Parameters
+ ----------
+ problem: Initializer
+ """
+ problem.mdp.data.to(DEVICE)
+ problem.mdp.device = DEVICE
+ problem.model.to(DEVICE)
+
+ state = [(0, 0, 0), (0, 5, 0)]
+ e_x = problem.mdp.encode_state(state, 0).to(DEVICE)
+ y = problem.mdp.encode_state(state, 0).to(DEVICE)
+ edge_index = problem.mdp.data.edge_index
+
+ print(summary(problem.model, e_x, edge_index, y))
+
+