From 8bcf3bd1cfa25906b158fc60cf763c570c75f99a Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 02:07:04 -0300 Subject: [PATCH 01/83] chore: workspace scaffold, pinned toolchain, CI Rust workspace for the mirror-pool contribution. Pins the arkworks major to 0.5 across every crate: light-poseidon 0.4 is built against 0.5, and Fr from 0.5 and 0.6 are distinct types that do not unify. Validated: groth16-solana + light-poseidon + ark-bn254 build for SBF under Agave 4.1.1 / platform-tools v1.54. --- .github/workflows/ci.yml | 43 + .gitignore | 9 + Cargo.lock | 2296 +++++++++++++++++++++++++++ Cargo.toml | 44 + crates/mirror-circuit/Cargo.toml | 19 + crates/mirror-circuit/src/lib.rs | 4 + crates/mirror-cli/Cargo.toml | 17 + crates/mirror-cli/src/main.rs | 4 + crates/mirror-core/Cargo.toml | 12 + crates/mirror-core/src/lib.rs | 3 + crates/mirror-provenance/Cargo.toml | 11 + crates/mirror-provenance/src/lib.rs | 4 + programs/mirror-pool/Cargo.toml | 15 + programs/mirror-pool/src/lib.rs | 4 + rust-toolchain.toml | 3 + 15 files changed, 2488 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 crates/mirror-circuit/Cargo.toml create mode 100644 crates/mirror-circuit/src/lib.rs create mode 100644 crates/mirror-cli/Cargo.toml create mode 100644 crates/mirror-cli/src/main.rs create mode 100644 crates/mirror-core/Cargo.toml create mode 100644 crates/mirror-core/src/lib.rs create mode 100644 crates/mirror-provenance/Cargo.toml create mode 100644 crates/mirror-provenance/src/lib.rs create mode 100644 programs/mirror-pool/Cargo.toml create mode 100644 programs/mirror-pool/src/lib.rs create mode 100644 rust-toolchain.toml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..b4dfadd1 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + branches: ["**"] + pull_request: + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -D warnings + +jobs: + check: + name: fmt / clippy / test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Cache cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - run: cargo fmt --all -- --check + - run: cargo clippy --workspace --all-targets --all-features + - run: cargo test --workspace --all-features + + program: + name: build-sbf + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Agave toolchain + run: | + sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" + echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH" + - run: solana --version + - name: Build the on-chain program + run: cargo build-sbf --manifest-path programs/mirror-pool/Cargo.toml + - name: Run the on-chain test suite against the built .so + run: cargo test -p mirror-pool-program --all-features diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..9dd59292 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +target/ +**/target/ +.DS_Store +*.so +!programs/**/fixtures/*.so +.env +keys/ +*.keypair.json +notes/ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..32c4cc38 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2296 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "ark-bn254" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a22f4561524cd949590d78d7d4c5df8f592430d221f7f3c9497bbafd8972120f" +dependencies = [ + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-std 0.4.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-crypto-primitives" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0c292754729c8a190e50414fd1a37093c786c709899f29c9f7daccecfa855e" +dependencies = [ + "ahash", + "ark-crypto-primitives-macros", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-relations", + "ark-serialize 0.5.0", + "ark-snark", + "ark-std 0.5.0", + "blake2", + "derivative", + "digest", + "fnv", + "merlin", + "sha2", +] + +[[package]] +name = "ark-crypto-primitives-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e89fe77d1f0f4fe5b96dfc940923d88d17b6a773808124f21e764dfb063c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff 0.4.2", + "ark-poly 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint", + "num-integer", + "num-traits", + "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", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest", + "educe", + "itertools 0.13.0", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[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-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[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-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-groth16" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88f1d0f3a534bb54188b8dcc104307db6c56cdae574ddc3212aec0625740fc7e" +dependencies = [ + "ark-crypto-primitives", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-poly 0.5.0", + "ark-relations", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-r1cs-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "941551ef1df4c7a401de7068758db6503598e6f01850bdb2cfdb614a1f9dbea1" +dependencies = [ + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-relations", + "ark-std 0.5.0", + "educe", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec46ddc93e7af44bcab5230937635b06fb5744464dd6a7e7b083e80ebd274384" +dependencies = [ + "ark-ff 0.5.0", + "ark-std 0.5.0", + "tracing", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive 0.4.2", + "ark-std 0.4.0", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-snark" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d368e2848c2d4c129ce7679a7d0d2d612b6a274d3ea6a13bad4445d61b381b88" +dependencies = [ + "ark-ff 0.5.0", + "ark-relations", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[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 = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[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.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "blake3" +version = "1.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aa83c34e62843d924f905e0f5c866eb1dd6545fc4d719e803d9ba6030371fce" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", + "cpufeatures 0.3.0", +] + +[[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 = "borsh" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f347189c62a579b8cd5f80714efa178f52e461dc2e6d701d264f5ff22e566c" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "cc" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5add81bb678e6cb321aff7fa0dc7689ad82b112dbc032cea19f91d6b8e3582b9" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rand_core", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +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 = "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 = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5e8f6c15a24b9a3ee5efec809ccd006d3b30e8b3bb63c39af737c7f87daa1d" + +[[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", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07f808d588c10e464ea6f7d3eaed500049eff30aaac103460f61828c2d65b3eb" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e528e2d34ba8a67a1a650b86beae8ef69fc5fdb638016f386b973226590432" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "five8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f76610e969fa1784327ded240f1e28a3fd9520c9cec93b636fcf62dd37f772" +dependencies = [ + "five8_core", +] + +[[package]] +name = "five8_const" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0f1728185f277989ca573a402716ae0beaaea3f76a8ff87ef9dd8fb19436c5" +dependencies = [ + "five8_core", +] + +[[package]] +name = "five8_core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059c31d7d36c43fe39d89e55711858b4da8be7eb6dabac23c7289b1a19489406" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "groth16-solana" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a6d1ffb18dbf5cfc60b11bd7da88474c672870247c1e5b498619bcb6ba3d8f5" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "num-bigint", + "solana-bn254", + "thiserror 1.0.69", +] + +[[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 = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", +] + +[[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.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[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", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "light-poseidon" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a1ccadd0bb5a32c196da536fd72c59183de24a055f6bf0513bf845fefab862" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "num-bigint", + "thiserror 1.0.69", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + +[[package]] +name = "mirror-circuit" +version = "0.1.0" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-groth16", + "ark-r1cs-std", + "ark-relations", + "ark-serialize 0.5.0", + "ark-snark", + "ark-std 0.5.0", + "light-poseidon", + "mirror-core", + "thiserror 2.0.19", +] + +[[package]] +name = "mirror-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "mirror-circuit", + "mirror-core", + "mirror-provenance", + "serde", + "serde_json", +] + +[[package]] +name = "mirror-core" +version = "0.1.0" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "light-poseidon", + "thiserror 2.0.19", +] + +[[package]] +name = "mirror-pool-program" +version = "0.1.0" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ff 0.5.0", + "groth16-solana", + "light-poseidon", + "solana-program", +] + +[[package]] +name = "mirror-provenance" +version = "0.1.0" +dependencies = [ + "anyhow", + "serde", + "serde_json", + "thiserror 2.0.19", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "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-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pastey" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[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 = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[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 = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "solana-account-info" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9cf16495d9eb53e3d04e72366a33bb1c20c24e78c171d8b8f5978357b63ae95" +dependencies = [ + "bincode", + "serde_core", + "solana-address", + "solana-program-error", + "solana-program-memory", +] + +[[package]] +name = "solana-address" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01332a01c0a3098404d55a724c8d9a92aed4a50fe40a7dd0c7a51e29274c14de" +dependencies = [ + "borsh", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "five8", + "five8_const", + "serde", + "serde_derive", + "sha2-const-stable", + "solana-atomic-u64", + "solana-define-syscall 5.1.0", + "solana-program-error", + "solana-sanitize", + "solana-sha256-hasher", + "wincode", +] + +[[package]] +name = "solana-atomic-u64" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085db4906d89324cef2a30840d59eaecf3d4231c560ec7c9f6614a93c652f501" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "solana-big-mod-exp" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c80fb6d791b3925d5ec4bf23a7c169ef5090c013059ec3ed7d0b2c04efa085" +dependencies = [ + "num-bigint", + "num-traits", + "solana-define-syscall 3.0.0", +] + +[[package]] +name = "solana-blake3-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7116e1d942a2432ca3f514625104757ab8a56233787e95144c93950029e31176" +dependencies = [ + "blake3", + "solana-define-syscall 4.0.1", + "solana-hash", +] + +[[package]] +name = "solana-bn254" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4420f125118732833f36facf96a27e7b78314b2d642ba07fa9ffdacd8d79e243" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-serialize 0.4.2", + "bytemuck", + "solana-define-syscall 2.3.0", + "thiserror 2.0.19", +] + +[[package]] +name = "solana-borsh" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c04abbae16f57178a163125805637b8a076175bb5c0002fb04f4792bea901cf7" +dependencies = [ + "borsh", +] + +[[package]] +name = "solana-clock" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7708bdb262fd9c257c3a56d4289297b10a3e821b8cba3f7cf42b49c40201ab9" +dependencies = [ + "serde", + "serde_derive", + "solana-get-sysvar", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-cpi" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dea26709d867aada85d0d3617db0944215c8bb28d3745b912de7db13a23280c" +dependencies = [ + "solana-account-info", + "solana-define-syscall 4.0.1", + "solana-instruction", + "solana-program-error", + "solana-pubkey", + "solana-stable-layout", +] + +[[package]] +name = "solana-define-syscall" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae3e2abcf541c8122eafe9a625d4d194b4023c20adde1e251f94e056bb1aee2" + +[[package]] +name = "solana-define-syscall" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9697086a4e102d28a156b8d6b521730335d6951bd39a5e766512bbe09007cee" + +[[package]] +name = "solana-define-syscall" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" + +[[package]] +name = "solana-define-syscall" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e14a4f604117f379840956a8fc8695e4c84f5b0ebed192f31f60d9b85d581d" + +[[package]] +name = "solana-epoch-rewards" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0788d74ee15778deecaa15ed1a1e37727ba954f86cbc35225450a1f2b5012969" +dependencies = [ + "serde", + "serde_derive", + "solana-get-sysvar", + "solana-hash", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-epoch-schedule" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8116e6ffa6002237d5ab5edcbda17f9ba66b6742c45a89c9fb40a94dbacd4c1d" +dependencies = [ + "serde", + "serde_derive", + "solana-get-sysvar", + "solana-program-error", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-epoch-stake" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "027e6d0b9e7daac5b2ac7c3f9ca1b727861121d9ef05084cf435ff736051e7c2" +dependencies = [ + "solana-define-syscall 5.1.0", + "solana-pubkey", +] + +[[package]] +name = "solana-example-mocks" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb265ff95e28eceda117e2e3d2d2a611ecbbfe911dfeeeecd1521814540ffab" +dependencies = [ + "serde", + "serde_derive", + "solana-hash", + "solana-instruction", + "solana-nonce", + "solana-pubkey", + "solana-sdk-ids", + "solana-system-interface", + "thiserror 2.0.19", +] + +[[package]] +name = "solana-fee-calculator" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef67f01cc6a0c72e99a08d0d484683f995de4c80e9568728fa77d1537f9b7e09" +dependencies = [ + "log", + "serde", + "serde_derive", +] + +[[package]] +name = "solana-get-sysvar" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef3bc859fc036ed490146793557386cbfae614ebba4adc704c37d94350824ed4" +dependencies = [ + "solana-address", + "solana-define-syscall 5.1.0", + "solana-program-error", +] + +[[package]] +name = "solana-hash" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0df9b01495ed31100aca97a7f5862d5e19ab1636d60d1a9f02391408dd9dec84" +dependencies = [ + "borsh", + "bytemuck", + "bytemuck_derive", + "five8", + "serde", + "serde_derive", +] + +[[package]] +name = "solana-instruction" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ebb0ffd19263051bc3f683fcc086134b8ff23af894dcb63f7563c7137b42f1" +dependencies = [ + "bincode", + "borsh", + "serde", + "serde_derive", + "solana-define-syscall 5.1.0", + "solana-instruction-error", + "solana-pubkey", +] + +[[package]] +name = "solana-instruction-error" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8e7db78c122d02189619490b1fef04a2a042c389662920617c0e6381fdf8fdd" +dependencies = [ + "num-traits", + "solana-program-error", +] + +[[package]] +name = "solana-instructions-sysvar" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e0732294560e88ecdb2bbc656e67383e9f88c78ec09469cef172f0d28cd1bcd" +dependencies = [ + "bitflags", + "solana-account-info", + "solana-instruction", + "solana-instruction-error", + "solana-program-error", + "solana-sanitize", + "solana-sdk-ids", + "solana-serialize-utils", + "solana-sysvar-id", +] + +[[package]] +name = "solana-keccak-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed1c0d16d6fdeba12291a1f068cdf0d479d9bff1141bf44afd7aa9d485f65ef8" +dependencies = [ + "sha3", + "solana-define-syscall 4.0.1", + "solana-hash", +] + +[[package]] +name = "solana-last-restart-slot" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5099e736c3c06c451b307a60637d69eb65b3144143ebeed899e2fd1134f4fc35" +dependencies = [ + "serde", + "serde_derive", + "solana-get-sysvar", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-msg" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "726b7cbbc6be6f1c6f29146ac824343b9415133eee8cce156452ad1db93f8008" +dependencies = [ + "solana-define-syscall 5.1.0", +] + +[[package]] +name = "solana-native-token" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8dd4c280dca9d046139eb5b7a5ac9ad10403fbd64964c7d7571214950d758f" + +[[package]] +name = "solana-nonce" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4172d5b33a0a38fcdb2af8f84406570dd51567267da96c28ad0f31fc11621c0" +dependencies = [ + "solana-fee-calculator", + "solana-hash", + "solana-pubkey", + "solana-sha256-hasher", +] + +[[package]] +name = "solana-program" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "778f08fb0eaf52c9a3bef2978247f7fab0ccfddc44cfddb936d5ad9f98ede886" +dependencies = [ + "memoffset", + "solana-account-info", + "solana-big-mod-exp", + "solana-blake3-hasher", + "solana-borsh", + "solana-clock", + "solana-cpi", + "solana-define-syscall 5.1.0", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-epoch-stake", + "solana-example-mocks", + "solana-fee-calculator", + "solana-hash", + "solana-instruction", + "solana-instruction-error", + "solana-instructions-sysvar", + "solana-keccak-hasher", + "solana-last-restart-slot", + "solana-msg", + "solana-native-token", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-program-option", + "solana-program-pack", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-secp256k1-recover", + "solana-serde-varint", + "solana-serialize-utils", + "solana-sha256-hasher", + "solana-short-vec", + "solana-slot-hashes", + "solana-slot-history", + "solana-stable-layout", + "solana-sysvar", + "solana-sysvar-id", +] + +[[package]] +name = "solana-program-entrypoint" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c9b0a1ff494e05f503a08b3d51150b73aa639544631e510279d6375f290997" +dependencies = [ + "solana-account-info", + "solana-define-syscall 4.0.1", + "solana-program-error", + "solana-pubkey", +] + +[[package]] +name = "solana-program-error" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f04fa578707b3612b095f0c8e19b66a1233f7c42ca8082fcb3b745afcc0add6" +dependencies = [ + "borsh", + "serde", + "serde_derive", +] + +[[package]] +name = "solana-program-memory" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4068648649653c2c50546e9a7fb761791b5ab0cda054c771bb5808d3a4b9eb52" +dependencies = [ + "solana-define-syscall 4.0.1", +] + +[[package]] +name = "solana-program-option" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a88006a9b8594088cec9027ab77caaaa258a2aaa2083d3f086c44b42e50aeab" + +[[package]] +name = "solana-program-pack" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7701cb15b90667ae1c89ef4ac35a59c61e66ce58ddee13d729472af7f41d59" +dependencies = [ + "solana-program-error", +] + +[[package]] +name = "solana-pubkey" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db719574990de7e8b0f55a8593ac92a5ccb42c8ce67b3e4bf05b139d5d9ee71" +dependencies = [ + "solana-address", +] + +[[package]] +name = "solana-rent" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f0d780bf8e8a1fe8b5b5fce1acad6b209485b86dec246e7523d5e4a8b7c7fc" +dependencies = [ + "serde", + "serde_derive", + "solana-get-sysvar", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-sanitize" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" + +[[package]] +name = "solana-sdk-ids" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def234c1956ff616d46c9dd953f251fa7096ddbaa6d52b165218de97882b7280" +dependencies = [ + "solana-address", +] + +[[package]] +name = "solana-sdk-macro" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8765316242300c48242d84a41614cb3388229ec353ba464f6fe62a733e41806f" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "solana-secp256k1-recover" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a1ad3ed7846631c88c71c5d2f21a2ecb6b61da333d9be173b6b061b35609ae" +dependencies = [ + "k256", + "solana-define-syscall 5.1.0", + "thiserror 2.0.19", +] + +[[package]] +name = "solana-serde-varint" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "950e5b83e839dc0f92c66afc124bb8f40e89bc90f0579e8ec5499296d27f54e3" +dependencies = [ + "serde", +] + +[[package]] +name = "solana-serialize-utils" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761357b0853c9623bf12c1d2314b3d6160a85b087b84c45224fb85766d22616b" +dependencies = [ + "solana-instruction-error", + "solana-pubkey", + "solana-sanitize", +] + +[[package]] +name = "solana-sha256-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7dc3011ea4c0334aaaa7e7128cb390ecf546b28d412e9bf2064680f57f588f" +dependencies = [ + "sha2", + "solana-define-syscall 4.0.1", + "solana-hash", +] + +[[package]] +name = "solana-short-vec" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75782529cc4521114c1ecd71c7e2cdebd41a6fe9844b5d088fd36ef08c57c36" + +[[package]] +name = "solana-slot-hashes" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "007d6bc909599eea325c9d09f7ff16ef6166c134876ff47a6a122d693d058dad" +dependencies = [ + "serde", + "serde_derive", + "solana-get-sysvar", + "solana-hash", + "solana-sdk-ids", + "solana-sysvar-id", +] + +[[package]] +name = "solana-slot-history" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4560fde841a6f2001aedc33b74fe99840ee3d56a71fe9b98ee332303b8597900" +dependencies = [ + "bv", + "serde", + "serde_derive", + "solana-get-sysvar", + "solana-sdk-ids", + "solana-sysvar-id", +] + +[[package]] +name = "solana-stable-layout" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9f6a291ba063a37780af29e7db14bdd3dc447584d8ba5b3fc4b88e2bbc982fa" +dependencies = [ + "solana-instruction", + "solana-pubkey", +] + +[[package]] +name = "solana-stake-history" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c736a6aa0e53b9d264d90b06589fc0996f49f882f3e71842ed754fc57ffc1a43" +dependencies = [ + "serde", + "serde_derive", + "solana-clock", + "solana-get-sysvar", + "solana-sdk-ids", + "solana-sysvar-id", +] + +[[package]] +name = "solana-system-interface" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55b54965bf0b76fa8e2b35376583efddd4d916618cfe595bf48c7d7b55a9e628" +dependencies = [ + "num-traits", + "solana-address", + "solana-msg", + "solana-program-error", +] + +[[package]] +name = "solana-sysvar" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ada7045bbfdd802af08fbc9c7e2b56ddabb20599d22e4c3840fcef5e7afb8324" +dependencies = [ + "base64", + "bincode", + "bytemuck", + "bytemuck_derive", + "lazy_static", + "serde", + "serde_derive", + "solana-account-info", + "solana-clock", + "solana-define-syscall 5.1.0", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-fee-calculator", + "solana-get-sysvar", + "solana-hash", + "solana-instruction", + "solana-last-restart-slot", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-pubkey", + "solana-rent", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-slot-hashes", + "solana-slot-history", + "solana-stake-history", + "solana-sysvar-id", +] + +[[package]] +name = "solana-sysvar-id" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17358d1e9a13e5b9c2264d301102126cf11a47fd394cdf3dec174fe7bc96e1de" +dependencies = [ + "solana-address", + "solana-sdk-ids", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[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.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[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.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wincode" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5d39d1a984eb7ae37afa348f058216d62a6d5f71640f4113a8114386c2a812a" +dependencies = [ + "pastey", + "proc-macro2", + "quote", + "thiserror 2.0.19", + "wincode-derive", +] + +[[package]] +name = "wincode-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb427b174d0f50b407f003623db1d892986e780651ee9d4231f3c9fe9ffcbb7" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "zerocopy" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a105cd7b140f6eeec8acff2ea38135d3cab283ada58540f629fe51e46696eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe976fb70c78cd64cccfe3a6fc142244e8a77b70959b30faf9d0ac37ee228eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..954565a0 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,44 @@ +[workspace] +resolver = "2" +members = [ + "crates/mirror-core", + "crates/mirror-circuit", + "crates/mirror-provenance", + "crates/mirror-cli", + "programs/mirror-pool", +] + +[workspace.package] +version = "0.1.0" +edition = "2021" +license = "MIT" +repository = "https://github.com/solanabr/mirror-pool" + +# The arkworks major must stay 0.5 across every crate: light-poseidon 0.4 is built +# against 0.5, and Fr from 0.5 and 0.6 are distinct types that will not unify. +[workspace.dependencies] +ark-bn254 = { version = "0.5", default-features = false, features = ["scalar_field"] } +ark-ff = { version = "0.5", default-features = false } +ark-ec = { version = "0.5", default-features = false } +ark-relations = { version = "0.5", default-features = false } +ark-r1cs-std = { version = "0.5", default-features = false } +ark-groth16 = { version = "0.5", default-features = false } +ark-snark = { version = "0.5", default-features = false } +ark-serialize = { version = "0.5", default-features = false } +ark-std = { version = "0.5", default-features = false } +light-poseidon = "0.4" +groth16-solana = "0.2" +solana-program = "4.0" +thiserror = "2" +anyhow = "1" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +mirror-core = { path = "crates/mirror-core" } +mirror-circuit = { path = "crates/mirror-circuit" } +mirror-provenance = { path = "crates/mirror-provenance" } + +[profile.release] +overflow-checks = true +lto = "fat" +codegen-units = 1 diff --git a/crates/mirror-circuit/Cargo.toml b/crates/mirror-circuit/Cargo.toml new file mode 100644 index 00000000..2b96b4c1 --- /dev/null +++ b/crates/mirror-circuit/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mirror-circuit" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +mirror-core.workspace = true +ark-bn254.workspace = true +ark-ff.workspace = true +ark-ec.workspace = true +ark-relations.workspace = true +ark-r1cs-std.workspace = true +ark-groth16.workspace = true +ark-snark.workspace = true +ark-serialize.workspace = true +ark-std.workspace = true +light-poseidon.workspace = true +thiserror.workspace = true diff --git a/crates/mirror-circuit/src/lib.rs b/crates/mirror-circuit/src/lib.rs new file mode 100644 index 00000000..7ecde9e3 --- /dev/null +++ b/crates/mirror-circuit/src/lib.rs @@ -0,0 +1,4 @@ +//! Groth16 membership circuit over BN254, its trusted-setup ceremony, and the +//! host-side prover. Verifying keys are exported in the byte layout the +//! on-chain `groth16-solana` verifier consumes. +#![forbid(unsafe_code)] diff --git a/crates/mirror-cli/Cargo.toml b/crates/mirror-cli/Cargo.toml new file mode 100644 index 00000000..5db9157d --- /dev/null +++ b/crates/mirror-cli/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "mirror-cli" +version.workspace = true +edition.workspace = true +license.workspace = true + +[[bin]] +name = "mirror" +path = "src/main.rs" + +[dependencies] +mirror-core.workspace = true +mirror-circuit.workspace = true +mirror-provenance.workspace = true +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs new file mode 100644 index 00000000..9f446aea --- /dev/null +++ b/crates/mirror-cli/src/main.rs @@ -0,0 +1,4 @@ +fn main() -> anyhow::Result<()> { + println!("mirror-pool CLI"); + Ok(()) +} diff --git a/crates/mirror-core/Cargo.toml b/crates/mirror-core/Cargo.toml new file mode 100644 index 00000000..49f6320b --- /dev/null +++ b/crates/mirror-core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "mirror-core" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ark-bn254.workspace = true +ark-ff.workspace = true +ark-serialize.workspace = true +light-poseidon.workspace = true +thiserror.workspace = true diff --git a/crates/mirror-core/src/lib.rs b/crates/mirror-core/src/lib.rs new file mode 100644 index 00000000..383669e1 --- /dev/null +++ b/crates/mirror-core/src/lib.rs @@ -0,0 +1,3 @@ +//! Field primitives shared by the circuit, the on-chain program and the host tooling: +//! Poseidon hashing, the incremental Merkle accumulator, commitments and nullifiers. +#![forbid(unsafe_code)] diff --git a/crates/mirror-provenance/Cargo.toml b/crates/mirror-provenance/Cargo.toml new file mode 100644 index 00000000..8c738fd8 --- /dev/null +++ b/crates/mirror-provenance/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "mirror-provenance" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +anyhow.workspace = true +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true diff --git a/crates/mirror-provenance/src/lib.rs b/crates/mirror-provenance/src/lib.rs new file mode 100644 index 00000000..0b1aae57 --- /dev/null +++ b/crates/mirror-provenance/src/lib.rs @@ -0,0 +1,4 @@ +//! Funding-provenance measurement over real on-chain data: partition an anonymity +//! set by where each member's capital came from and report effective-k rather than +//! nominal k. This is a measurement crate; it makes no privacy claims by itself. +#![forbid(unsafe_code)] diff --git a/programs/mirror-pool/Cargo.toml b/programs/mirror-pool/Cargo.toml new file mode 100644 index 00000000..6d225972 --- /dev/null +++ b/programs/mirror-pool/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "mirror-pool-program" +version.workspace = true +edition.workspace = true +license.workspace = true + +[lib] +crate-type = ["cdylib", "lib"] + +[dependencies] +solana-program.workspace = true +groth16-solana.workspace = true +light-poseidon.workspace = true +ark-bn254.workspace = true +ark-ff.workspace = true diff --git a/programs/mirror-pool/src/lib.rs b/programs/mirror-pool/src/lib.rs new file mode 100644 index 00000000..97806959 --- /dev/null +++ b/programs/mirror-pool/src/lib.rs @@ -0,0 +1,4 @@ +//! mirror-pool: a behavioral anonymity set. A member's protocol action is executed +//! by the pool PDA, gated by a Groth16 membership proof verified on-chain, so an +//! observer sees that an action happened but cannot attribute it to a member. +#![forbid(unsafe_code)] diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..8565ca6b --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,3 @@ +[toolchain] +channel = "1.97.1" +components = ["rustfmt", "clippy"] From cb35da0a2745a18fd482b2596c18649b310da3df Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 03:15:52 -0300 Subject: [PATCH 02/83] docs: implementation plan and verified Groth16 integration reference The groth16-solana integration notes were verified by execution against the pinned versions rather than taken from documentation, which is stale: it shows an API removed in 0.0.3 and arkworks 0.3 traits that no longer exist. Records the G2 c1/c0 cross order, the mandatory proof_a negation, and the measured CU curve (74,179 + 5,661 per public input) that drives the 3-input circuit design. Also corrects ark-bn254 to features=["curve"]; scalar_field alone yields Fr but not the curve groups. --- Cargo.toml | 2 +- docs/GROTH16_INTEGRATION.md | 106 ++++++++++++++++++ docs/PLAN.md | 215 ++++++++++++++++++++++++++++++++++++ 3 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 docs/GROTH16_INTEGRATION.md create mode 100644 docs/PLAN.md diff --git a/Cargo.toml b/Cargo.toml index 954565a0..f6b44335 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ repository = "https://github.com/solanabr/mirror-pool" # The arkworks major must stay 0.5 across every crate: light-poseidon 0.4 is built # against 0.5, and Fr from 0.5 and 0.6 are distinct types that will not unify. [workspace.dependencies] -ark-bn254 = { version = "0.5", default-features = false, features = ["scalar_field"] } +ark-bn254 = { version = "0.5", default-features = false, features = ["curve"] } ark-ff = { version = "0.5", default-features = false } ark-ec = { version = "0.5", default-features = false } ark-relations = { version = "0.5", default-features = false } diff --git a/docs/GROTH16_INTEGRATION.md b/docs/GROTH16_INTEGRATION.md new file mode 100644 index 00000000..2bcbfcf4 --- /dev/null +++ b/docs/GROTH16_INTEGRATION.md @@ -0,0 +1,106 @@ +# arkworks 0.5 → `groth16-solana` 0.2: integration reference + +Every claim here was verified by execution against the pinned versions, not taken +from documentation. The published `groth16-solana` docs are stale (they show an +API removed in 0.0.3 and arkworks 0.3 traits that no longer exist), so this file +is the authority for our conversion code. + +Pinned: `ark-groth16 0.5`, `ark-bn254 0.5`, `groth16-solana 0.2`, +`solana-program 4.0`, `solana-bn254 2.2.2`. + +## Byte layouts + +| Item | Layout | Size | +|---|---|---| +| G1 | `x_be ‖ y_be` | 64 | +| G2 | `x.c1_be ‖ x.c0_be ‖ y.c1_be ‖ y.c0_be` | 128 | +| Public input | canonical `Fr`, big-endian | 32 | +| Point at infinity | all zero bytes | 64 / 128 | + +The G2 **cross order** is the single easiest thing to get wrong. `solana-bn254`'s +`PodG2::from_be_bytes` reads `c1` before `c0` (its own source comments it "note +the cross order"). arkworks serializes `Fq2` as `c0_le ‖ c1_le`, so reversing the +whole 64-byte block flips limb endianness *and* swaps the components in one step. +G1 therefore reverses 32-byte chunks; G2 reverses **64-byte** chunks. + +## `proof_a` must be pre-negated + +`Groth16Verifier::new` performs length checks only — no arithmetic — and +`verify_common` feeds `proof_a` directly into the four-pair product +`e(A,B)·e(Σic,γ)·e(C,δ)·e(α,β) == 1`, which holds only when `A = −A_proof`. +Serialize `-proof.a`. Nothing negates it for you; the crate's own negative test +passes a non-negated `proof_a` specifically to assert failure. + +## Verifying key + +```rust +pub struct Groth16Verifyingkey<'a> { + pub nr_pubinputs: usize, + pub vk_alpha_g1: [u8; 64], + pub vk_beta_g2: [u8; 128], + pub vk_gamme_g2: [u8; 128], // the typo is the real field name + pub vk_delta_g2: [u8; 128], + pub vk_ic: &'a [[u8; 64]], +} +``` + +`vk_ic.len() == n + 1` for `n` public inputs; `vk_ic[0]` is the constant term and +`vk_ic[i+1]` multiplies input `i`, in the order inputs were allocated in the +circuit. `nr_pubinputs` is **never read** by any verification path, and the +upstream JS generator sets it to `n + 1` rather than `n` — do not rely on it. The +const generic `NR_INPUTS` on `Groth16Verifier` is the real input count, and +`public_inputs` is `&[[u8; 32]; NR_INPUTS]`, a fixed-size array reference rather +than a slice. + +`verify()` returns `Result<(), Groth16Error>`; there is no `Ok(false)`. + +## Compute units + +Measured in LiteSVM against a real SBF build: + +| Public inputs | `verify()` | +|---|---| +| 1 | 79,840 | +| 2 | 85,500 | +| 4 | 96,822 | +| 8 | 119,466 | + +`verify() ≈ 74,179 + 5,661·N`. The pairing itself is a fixed 73,612 CU; each +additional public input costs one G1 multiplication (3,840), one addition (334), +allocation overhead (~510) and a canonical-range check (~977). + +**Design consequence:** public inputs are the only knob. Every input we can fold +into a hash saves ~5.7k CU, so the circuit binds a single digest wherever several +values would otherwise be exposed separately. + +`verify_unchecked` saves ~977 CU per input by skipping the `< r` range check. It +is safe only for inputs we constructed ourselves from `Fr`; never for +attacker-supplied bytes. We use the checked form on anything that crosses the +instruction boundary. + +## Pitfalls + +1. **Serialize coordinates, not the point.** arkworks packs a y-sign flag into + the top bit of y's most significant byte even under `Compress::No` — it fires + on roughly half of all points. Verification still succeeds (deserialization + masks flags), but the bytes are non-canonical, which silently breaks anything + that hashes or compares serialized proofs and keys. Write `p.x` and `p.y` + separately. +2. **Infinity encodes as all zeros** in the syscall ABI, but arkworks writes flag + `0x40`. An explicit infinity branch is required. Honest keys never contain + infinity; adversarial input can. +3. **Uncompressed sizes are 64 and 128**, not 65/129. A trailing byte appears in + some upstream examples as arkworks-0.3 residue; it is vestigial. +4. **Compressed input is not accepted.** Decompression costs 398 CU for G1 but + **13,610** for G2, so compressing to save transaction bytes is rarely worth it. +5. **`ark-bn254` needs `features = ["curve"]`** when `default-features = false`; + `scalar_field` alone gives `Fr` but not the curve groups. +6. **Duplicate arkworks in the lockfile is expected.** `groth16-solana` pulls + `solana-bn254`, which pins arkworks 0.4 — but only under + `cfg(not(target_os = "solana"))`, so it is host-only and never linked into the + `.so`. Types from different arkworks majors never unify; keep ours on 0.5. +7. **Subgroup and curve checks happen inside the syscall**, not in `new()`. They + cannot be skipped and need not be duplicated. +8. **`cargo-build-sbf` resolves dependencies with the host cargo.** A host + toolchain older than 1.85 fails on transitive `edition2024` crates. Our + `rust-toolchain.toml` pins 1.97.1, which covers this. diff --git a/docs/PLAN.md b/docs/PLAN.md new file mode 100644 index 00000000..432d19af --- /dev/null +++ b/docs/PLAN.md @@ -0,0 +1,215 @@ +# mirror-pool — implementation plan + +## The thesis + +`mirror-pool` is meant to be a crowd-sourced privacy set: users pool their +activity so that an observer sees an action happened but cannot say who +initiated it. The hard part is not the proof system. It is that **an anonymity +set on a public ledger can be partitioned by where each member's capital came +from**, and once you sort a set of `k` members into funding-provenance classes, +the anonymity that remains is the size of the actor's class, not `k`. + +This is the one problem every serious submission across all three repos of this +bounty concedes and none closes. So it is the problem this contribution takes as +its subject. + +We do not claim to hide provenance. Value moves one way on a ledger, and no +deposit pool controls where its users' money came from. What we claim is +narrower and defensible: + +1. **The action side is closed.** Actions are executed by the pool PDA, so the + on-chain funding trace of an action leads to the pool and is identical for + every member. +2. **The membership side is measured, not asserted.** Effective anonymity is + computed from real mainnet chain data at several label resolutions, and the + method and its limits are published with the number. +3. **The measurement is load-bearing.** A pool that cannot demonstrate an + effective-`k` above a floor refuses to settle. The metric is a protocol + parameter, not a marketing line. + +If we cannot support a claim with a measurement whose method we publish, we do +not make the claim. + +## What we will not claim + +- Not "unlinkable", not "untraceable", not "anonymous" without a named adversary + and a stated population. +- Not that the funding-provenance channel is closed. It is reduced on the action + side and measured on the membership side. +- Not that a single-contributor trusted setup is secure. It is reproducible, + which is a different and lesser property, and we say which one we have. +- No number in any document may come from a synthetic fixture presented as a + measurement. Every reported figure names its data source. + +## Architecture + +### Pool model — fixed denomination, note-based + +One pool instance serves exactly one denomination `D`. A deposit escrows exactly +`D` plus an entry fee and inserts a note commitment into an incremental Poseidon +Merkle accumulator. A spend proves membership of some note, publishes its +nullifier, and directs the pool to execute one action disbursing exactly +`D − relay_fee`. + +``` +note = (k, r) secret nullifier preimage, blinding +commitment = Poseidon(k, r) the Merkle leaf +nullifier = Poseidon(k) revealed on spend, spent once ever +``` + +The denomination is a pool constant, so it never needs to be bound into the +commitment and can never disagree with the escrowed amount. + +**Accounting invariant**, asserted in tests: +`vault.lamports >= D × (deposits − spends) + rent_exempt_minimum`. + +This is deliberate. Two competing submissions are drainable precisely here: one +escrows an amount that is never bound to the hidden commitment, so a depositor of +one lamport can withdraw the pool with a valid proof; the other issues an +epoch-scoped nullifier against a value payout, so a single deposit pays out once +per epoch forever. A fixed denomination with spend-once nullifiers makes both +failures unrepresentable rather than merely untested. + +### Circuit — 3 public inputs + +The statement: *I know `(k, r)` such that `Poseidon(k, r)` is a leaf of the tree +with root `R`, my nullifier is `Poseidon(k)`, and this proof is bound to +`action`.* + +| Public input | Why it must be public | +|---|---| +| `root` | the program checks it against its own root history | +| `nullifier` | the program records it to prevent replay | +| `action_binding` | the program recomputes it, so a relay cannot redirect or re-price | + +Measured cost is `74,179 + 5,661 × N` CU, so three inputs land near 91k CU — +comparable to the strongest competing implementation while binding strictly more. +Any further value we need to commit to gets folded into `action_binding` rather +than added as a fourth input. See `GROTH16_INTEGRATION.md` for the verified +conversion path and its pitfalls. + +The action binding covers the action selector, its parameters, the beneficiary +and the relay fee, so every economically meaningful field is inside the proof. + +### Two-phase epochs — synchronization without a CU explosion + +The bounty's premise is synchronized crowds: many identical actions landing +together so that timing and ordering carry no signal. Verifying `N` proofs in one +transaction does not fit — three proofs exhaust the transaction size limit long +before the compute limit. + +So the protocol splits it: + +- **Phase 1, any time during the epoch.** A member submits their proof in its own + relay-signed transaction. The program verifies it, burns the nullifier and + writes a ticket PDA recording the bound action. Nothing executes yet. +- **Phase 2, at epoch close.** The pool executes every ticket's action in one + settlement, sharing a single timestamp and ordering. + +This also fixes a structural leak in a competing design, where every participant +must sign the settlement transaction — which publishes the entire membership set +by pubkey in a single transaction. Here no member key ever appears on chain. + +### Relays, and never holding users hostage + +A member who pays their own fee signs with their own wallet and destroys their +own anonymity, so spends are relay-signed and the relay is paid out of `D`. + +Two rules follow, both of which a competing submission violates: + +- **Relaying is permissionless.** Any key may relay. There is no single immutable + authority whose loss freezes the pool. (In the competing design the pool + authority is baked into the PDA seeds with no rotation instruction, so the + advertised "gasless rotating relay" cannot be deployed at all.) +- **There is always an exit.** A member may always self-spend, paying their own + fee and accepting the privacy loss, so funds are never hostage to a relay's + liveness. + +### Incentives + +Entry fees accumulate in a reward pool paid pro-rata to *dwell* — how long a note +stayed unspent — which is the behaviour that makes everyone else's set larger. +Payouts are capped per epoch and gated on the `k` floor, so a lone participant +cannot recycle their own fee back to themselves, which is a live flaw in one +competitor's implementation and asserted by its own test. + +### Provenance measurement + +A separate crate collects real mainnet funding data and reports effective +anonymity at several label resolutions, with the raw sample committed so results +reproduce without RPC access. Methodology is being finalised; the design targets +the specific weaknesses found in the two published attempts: selection bias that +inflates the untraceable bucket, a sample spanning four seconds of chain time +presented as a population, and a class key defined at raw-address resolution so +that two members funded from the same exchange land in different classes. + +## Schedule + +92 hours remain at the time of writing. Deadline is 2026-07-28 23:59 BRT. + +### Day 1 — primitives and circuit +`mirror-core`: Poseidon over BN254, the incremental Merkle accumulator with its +zero ladder, note commitments and nullifiers, and the byte layouts shared with +the program. `mirror-circuit`: the R1CS statement, key generation, the host +prover, and export of the verifying key in the on-chain layout. + +*Done when* a proof generated on the host verifies against the exported +verifying-key bytes, and an in-circuit Merkle root equals one computed natively. + +### Day 2 — the on-chain program +Instructions: `init_pool`, `deposit`, `open_epoch`, `submit_spend` (verify, burn +nullifier, write ticket), `settle_epoch`, `claim_reward`, `self_spend`. State: +pool, epoch, frontier accumulator with root history, nullifier PDAs, tickets. + +*Done when* the suite runs against the built `.so` and covers the accounting +invariant, replay rejection, `k`-floor rejection, action-binding mismatch, +malformed input on every handler, and a drain attempt of the shape that breaks +the competing implementations. CI green on fmt, clippy, tests and `build-sbf`. + +### Day 3 — measurement, CLI, setup ceremony +`mirror-provenance` against real mainnet data with the sample committed. The CLI +end to end: keygen, deposit, prove, spend, claim, and an `audit` command that +recomputes effective-`k` from committed data. Setup with a transcript that binds +the **full** verifying key and a circuit digest, and a `verify-setup` any third +party can run — closing the gap where a competitor's ceremony verifier checks +only `delta` and would green-light a verifying key belonging to a different +circuit. + +### Day 4 — deploy, evidence, submission +Devnet soak producing signatures for every flow plus on-chain negative cases with +their error codes. Mainnet deployment — no competitor has their own pool on +mainnet. Documentation: README, ARCHITECTURE, THREAT_MODEL with honest limits, +PROOF, EFFECTIVE_K. Open the PR with time for CI to finish, and submit on Earn. + +### Cut lines, in the order things get dropped + +Multi-party ceremony support degrades to a reproducible single-contributor setup +with the multi-party path documented. Dwell rewards degrade to a flat entry fee. +The provenance sample shrinks before its method weakens. **The accounting +invariant, the negative tests, the honest limitations section and a green CI are +never cut** — they are what separates this from the submissions it means to beat. + +### Stretch, only if the above is complete + +A composability contribution to `account-cooker`, funding an agent fleet through +mirror-pool so that every agent wallet shares one provenance class. That repo's +strongest submission admits the observable common-funder graph is what defeats +it, and names a shielded funding path as an explicit non-goal. It is a second +prize with a separate champion, and the work is small once the pool exists. + +## Risks + +**The circuit costs more than a day.** Mitigated: the two integration unknowns +that usually eat that day — the arkworks-to-Solana byte layout and the +Poseidon-to-gadget agreement — were resolved before implementation started. + +**RPC capacity for the provenance sample.** Mitigated by committing the raw +sample so the result reproduces offline, and by sizing the sample to the access +we actually have rather than to the number we would like to report. + +**Mainnet deployment cost.** A program of this size is a few SOL to deploy. +Devnet evidence is the fallback and is on the critical path regardless. + +**Competitors keep iterating.** Two of the three updated their branches within +the last day. We do not race their line count; we ship the thing all of them +declare open, and we make every number checkable. From ee0b25d6edb105f6d871953aeb7b0e7df171533e Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 03:27:35 -0300 Subject: [PATCH 03/83] feat(core): canonical field, domain-separated Poseidon, Merkle accumulator Poseidon goes through solana-poseidon only, which is cfg-gated upstream to the syscall on-chain and light-poseidon off-chain, so host and program compute one function by construction. poseidon2(1,2) is pinned against circomlib's published vector, so the gadget added next is checked against an external constant rather than against our own implementation. Domain separation is by arity rather than by an integer tag. The tag design was tried first and was wrong: a Merkle node whose left child equals a small tag constant collides with a nullifier. Notes bind their denomination, and nullifiers are spend-once rather than epoch-scoped, which makes both of the drain bugs found in competing submissions unrepresentable rather than merely untested. Frontier (on-chain view) and MerkleTree (host view) are asserted to agree over real insertion sequences, and every host path is folded against the accumulator root. 23 tests, clippy clean under -D warnings. --- Cargo.lock | 51 +++++- Cargo.toml | 11 ++ crates/mirror-circuit/Cargo.toml | 6 + crates/mirror-core/Cargo.toml | 8 +- crates/mirror-core/src/field.rs | 155 ++++++++++++++++ crates/mirror-core/src/hash.rs | 180 +++++++++++++++++++ crates/mirror-core/src/lib.rs | 34 +++- crates/mirror-core/src/merkle.rs | 298 +++++++++++++++++++++++++++++++ crates/mirror-core/src/note.rs | 76 ++++++++ programs/mirror-pool/Cargo.toml | 4 +- 10 files changed, 806 insertions(+), 17 deletions(-) create mode 100644 crates/mirror-core/src/field.rs create mode 100644 crates/mirror-core/src/hash.rs create mode 100644 crates/mirror-core/src/merkle.rs create mode 100644 crates/mirror-core/src/note.rs diff --git a/Cargo.lock b/Cargo.lock index 32c4cc38..8fbef883 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,6 +58,7 @@ dependencies = [ "ark-crypto-primitives-macros", "ark-ec 0.5.0", "ark-ff 0.5.0", + "ark-r1cs-std", "ark-relations", "ark-serialize 0.5.0", "ark-snark", @@ -66,8 +67,10 @@ dependencies = [ "derivative", "digest", "fnv", + "hashbrown 0.14.5", "merlin", "sha2", + "tracing", ] [[package]] @@ -874,6 +877,15 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "allocator-api2", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -973,6 +985,18 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "light-poseidon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ff 0.4.2", + "num-bigint", + "thiserror 1.0.69", +] + [[package]] name = "light-poseidon" version = "0.4.0" @@ -1032,6 +1056,7 @@ name = "mirror-circuit" version = "0.1.0" dependencies = [ "ark-bn254 0.5.0", + "ark-crypto-primitives", "ark-ec 0.5.0", "ark-ff 0.5.0", "ark-groth16", @@ -1040,8 +1065,9 @@ dependencies = [ "ark-serialize 0.5.0", "ark-snark", "ark-std 0.5.0", - "light-poseidon", + "light-poseidon 0.4.0", "mirror-core", + "solana-poseidon", "thiserror 2.0.19", ] @@ -1061,10 +1087,7 @@ dependencies = [ name = "mirror-core" version = "0.1.0" dependencies = [ - "ark-bn254 0.5.0", - "ark-ff 0.5.0", - "ark-serialize 0.5.0", - "light-poseidon", + "solana-poseidon", "thiserror 2.0.19", ] @@ -1072,10 +1095,8 @@ dependencies = [ name = "mirror-pool-program" version = "0.1.0" dependencies = [ - "ark-bn254 0.5.0", - "ark-ff 0.5.0", "groth16-solana", - "light-poseidon", + "mirror-core", "solana-program", ] @@ -1715,6 +1736,20 @@ dependencies = [ "solana-sha256-hasher", ] +[[package]] +name = "solana-poseidon" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "737b8ab25bf4cc8e618f80f1fe40709b2ace708bc764a36b8a4c81eea8c07034" +dependencies = [ + "ark-bn254 0.4.0", + "ark-bn254 0.5.0", + "light-poseidon 0.2.0", + "light-poseidon 0.4.0", + "solana-define-syscall 4.0.1", + "thiserror 2.0.19", +] + [[package]] name = "solana-program" version = "4.0.0" diff --git a/Cargo.toml b/Cargo.toml index f6b44335..92c0e191 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,18 @@ ark-groth16 = { version = "0.5", default-features = false } ark-snark = { version = "0.5", default-features = false } ark-serialize = { version = "0.5", default-features = false } ark-std = { version = "0.5", default-features = false } +# solana-poseidon is deliberately the ONLY Poseidon entry point. It is cfg-gated +# upstream: the `sol_poseidon` syscall on target_os = "solana", light-poseidon 0.4 +# over ark-bn254 0.5 off-chain. That gives host/chain parity for free and keeps +# arkworks out of the .so — a pure-Rust Poseidon on SBF overflows the 4096-byte +# stack frame and costs ~1500x the syscall even when it completes. +solana-poseidon = "4.0" +# Host-only, and only for its published round constants, which the R1CS gadget +# reads so that in-circuit hashing provably matches the syscall. light-poseidon = "0.4" +# 0.5 gates constraints behind `r1cs`; 0.6 renamed it to `constraints` and moved +# to ark_relations::gr1cs, which does not compose with our 0.5 pin. +ark-crypto-primitives = { version = "0.5", default-features = false, features = ["sponge", "crh", "merkle_tree", "r1cs"] } groth16-solana = "0.2" solana-program = "4.0" thiserror = "2" diff --git a/crates/mirror-circuit/Cargo.toml b/crates/mirror-circuit/Cargo.toml index 2b96b4c1..0613a700 100644 --- a/crates/mirror-circuit/Cargo.toml +++ b/crates/mirror-circuit/Cargo.toml @@ -4,6 +4,8 @@ version.workspace = true edition.workspace = true license.workspace = true +# Host-only crate: proving, key generation and the R1CS gadget. Never linked +# into the on-chain program. [dependencies] mirror-core.workspace = true ark-bn254.workspace = true @@ -15,5 +17,9 @@ ark-groth16.workspace = true ark-snark.workspace = true ark-serialize.workspace = true ark-std.workspace = true +ark-crypto-primitives.workspace = true light-poseidon.workspace = true thiserror.workspace = true + +[dev-dependencies] +solana-poseidon.workspace = true diff --git a/crates/mirror-core/Cargo.toml b/crates/mirror-core/Cargo.toml index 49f6320b..20e8deef 100644 --- a/crates/mirror-core/Cargo.toml +++ b/crates/mirror-core/Cargo.toml @@ -4,9 +4,9 @@ version.workspace = true edition.workspace = true license.workspace = true +# No arkworks here. mirror-core is linked into the on-chain program, and every +# byte of arkworks in the .so is a stack frame we cannot afford. Field elements +# cross this boundary as canonical 32-byte big-endian arrays. [dependencies] -ark-bn254.workspace = true -ark-ff.workspace = true -ark-serialize.workspace = true -light-poseidon.workspace = true +solana-poseidon.workspace = true thiserror.workspace = true diff --git a/crates/mirror-core/src/field.rs b/crates/mirror-core/src/field.rs new file mode 100644 index 00000000..a4dbb86b --- /dev/null +++ b/crates/mirror-core/src/field.rs @@ -0,0 +1,155 @@ +//! Canonical BN254 scalar field elements. +//! +//! Everything that crosses the boundary between the circuit, the host and the +//! on-chain program is a `Field`: exactly 32 bytes, big-endian, strictly less +//! than the BN254 scalar modulus `r`. +//! +//! The canonicality check is not decoration. SIMD-0359 made the Poseidon +//! syscall reject inputs that are not full field elements, and the on-chain +//! Groth16 verifier rejects public inputs greater than or equal to `r`. Values +//! that fail either check must be rejected where they enter the system, not +//! silently reduced — reducing would let two distinct byte strings denote one +//! nullifier, which is a double-spend. + +use crate::MirrorError; + +/// The BN254 scalar field modulus `r`, big-endian. +/// +/// 21888242871839275222246405745257275088548364400416034343698204186575808495617 +pub const MODULUS_BE: [u8; 32] = [ + 0x30, 0x64, 0x4e, 0x72, 0xe1, 0x31, 0xa0, 0x29, 0xb8, 0x50, 0x45, 0xb6, 0x81, 0x81, 0x58, 0x5d, + 0x28, 0x33, 0xe8, 0x48, 0x79, 0xb9, 0x70, 0x91, 0x43, 0xe1, 0xf5, 0x93, 0xf0, 0x00, 0x00, 0x01, +]; + +/// A canonical BN254 scalar, stored big-endian. +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Field([u8; 32]); + +impl Field { + /// Additive identity. + pub const ZERO: Field = Field([0u8; 32]); + + /// Wraps 32 big-endian bytes, rejecting anything at or above the modulus. + pub fn from_bytes(bytes: [u8; 32]) -> Result { + if !is_canonical(&bytes) { + return Err(MirrorError::NonCanonicalField); + } + Ok(Field(bytes)) + } + + /// Wraps a small integer, which is always canonical. + pub fn from_u64(v: u64) -> Self { + let mut bytes = [0u8; 32]; + bytes[24..].copy_from_slice(&v.to_be_bytes()); + Field(bytes) + } + + /// Reads a canonical element from a slice of exactly 32 bytes. + pub fn from_slice(bytes: &[u8]) -> Result { + let arr: [u8; 32] = bytes.try_into().map_err(|_| MirrorError::BadFieldLength)?; + Self::from_bytes(arr) + } + + pub const fn to_bytes(self) -> [u8; 32] { + self.0 + } + + pub const fn as_bytes(&self) -> &[u8; 32] { + &self.0 + } + + pub fn is_zero(&self) -> bool { + self.0 == [0u8; 32] + } +} + +/// Constant-time-ish big-endian comparison against the modulus. +/// +/// This runs on values that are already public (roots, nullifiers, bindings), so +/// timing is not a concern; the loop is written for clarity and to avoid pulling +/// a bignum dependency into the on-chain binary. +fn is_canonical(bytes: &[u8; 32]) -> bool { + for i in 0..32 { + match bytes[i].cmp(&MODULUS_BE[i]) { + core::cmp::Ordering::Less => return true, + core::cmp::Ordering::Greater => return false, + core::cmp::Ordering::Equal => continue, + } + } + // Exactly equal to the modulus is not a canonical field element. + false +} + +impl core::fmt::Debug for Field { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "Field(0x")?; + for b in self.0 { + write!(f, "{b:02x}")?; + } + write!(f, ")") + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zero_and_small_values_are_canonical() { + assert!(Field::from_bytes([0u8; 32]).is_ok()); + assert_eq!(Field::from_u64(0), Field::ZERO); + assert_eq!(Field::from_u64(1).to_bytes()[31], 1); + } + + #[test] + fn the_modulus_itself_is_rejected() { + // r is not a member of the field; only 0..r-1 are. + assert!(matches!( + Field::from_bytes(MODULUS_BE), + Err(MirrorError::NonCanonicalField) + )); + } + + #[test] + fn one_below_the_modulus_is_accepted_and_one_above_is_not() { + let mut below = MODULUS_BE; + below[31] -= 1; + assert!(Field::from_bytes(below).is_ok()); + + let mut above = MODULUS_BE; + above[31] += 1; + assert!(Field::from_bytes(above).is_err()); + } + + #[test] + fn all_ones_is_rejected() { + // The classic non-canonical input an attacker reaches for first. + assert!(Field::from_bytes([0xff; 32]).is_err()); + } + + #[test] + fn a_wrong_length_slice_is_rejected_rather_than_padded() { + assert!(matches!( + Field::from_slice(&[1u8; 31]), + Err(MirrorError::BadFieldLength) + )); + assert!(matches!( + Field::from_slice(&[1u8; 33]), + Err(MirrorError::BadFieldLength) + )); + } + + #[test] + fn high_byte_boundary_is_respected() { + // Differs from the modulus only in the first byte, one below: canonical. + let mut b = MODULUS_BE; + b[0] -= 1; + assert!(Field::from_bytes(b).is_ok()); + + // One above in the first byte: not canonical, even though later bytes are small. + let mut b = MODULUS_BE; + b[0] += 1; + b[31] = 0; + assert!(Field::from_bytes(b).is_err()); + } +} diff --git a/crates/mirror-core/src/hash.rs b/crates/mirror-core/src/hash.rs new file mode 100644 index 00000000..0d0fef8f --- /dev/null +++ b/crates/mirror-core/src/hash.rs @@ -0,0 +1,180 @@ +//! Poseidon over BN254, with domain separation by arity. +//! +//! `solana-poseidon` is the only entry point, deliberately. Upstream it is +//! cfg-gated to the `sol_poseidon` syscall on-chain and to light-poseidon over +//! ark-bn254 off-chain, so the host and the program compute the same function by +//! construction rather than by our own careful duplication. The R1CS gadget in +//! `mirror-circuit` is checked against this, which closes the three-way parity: +//! gadget == host == syscall. +//! +//! ## Domain separation +//! +//! Poseidon instances of different width are different permutations, so arity is +//! itself a domain separator — and it is a free one. Each hashed value in the +//! protocol uses a distinct arity, so no preimage of one can ever be reread as a +//! preimage of another: +//! +//! | value | arity | preimage | +//! |---|---|---| +//! | nullifier | 1 | `(k)` | +//! | Merkle node | 2 | `(left, right)` | +//! | note commitment | 3 | `(k, r, denom_tag)` | +//! | action binding | 4 | `(selector, beneficiary_hi, beneficiary_lo, params)` | +//! +//! An explicit integer tag was the first design here and it was wrong: with a +//! small tag constant, a Merkle node whose left child equals the tag collides +//! with a nullifier. Reaching that state requires a Poseidon preimage and so is +//! not practically exploitable, but arity separation removes the question +//! entirely at no cost. +//! +//! A competing implementation in this bounty hashes its Merkle nodes, its +//! nullifiers and its action bindings with one untagged arity-2 function. + +use crate::{Field, MirrorError}; +use solana_poseidon::{hashv, Endianness, Parameters}; + +fn digest(inputs: &[&[u8]]) -> Result { + let out = hashv(Parameters::Bn254X5, Endianness::BigEndian, inputs) + .map_err(|_| MirrorError::Poseidon)?; + // The syscall reduces into the field, so this cannot fail. We still go + // through the checked constructor rather than assume it. + Field::from_bytes(out.to_bytes()) +} + +/// Arity-1 Poseidon. +pub fn poseidon1(a: Field) -> Result { + digest(&[a.as_bytes()]) +} + +/// Arity-2 Poseidon. +pub fn poseidon2(a: Field, b: Field) -> Result { + digest(&[a.as_bytes(), b.as_bytes()]) +} + +/// Arity-3 Poseidon. +pub fn poseidon3(a: Field, b: Field, c: Field) -> Result { + digest(&[a.as_bytes(), b.as_bytes(), c.as_bytes()]) +} + +/// Arity-4 Poseidon. +pub fn poseidon4(a: Field, b: Field, c: Field, d: Field) -> Result { + digest(&[a.as_bytes(), b.as_bytes(), c.as_bytes(), d.as_bytes()]) +} + +/// Merkle internal node: `H2(left, right)`. +pub fn hash_node(left: Field, right: Field) -> Result { + poseidon2(left, right) +} + +/// Note commitment: `H3(k, r, denom_tag)`. +/// +/// `k` is the nullifier preimage, `r` the blinding factor. `denom_tag` pins the +/// commitment to the pool's denomination so a note can never be replayed into a +/// pool of a different size even if the two share an accumulator. +pub fn commitment(k: Field, r: Field, denom_tag: Field) -> Result { + poseidon3(k, r, denom_tag) +} + +/// Nullifier: `H1(k)`. +/// +/// Deliberately not scoped to an epoch. An epoch-scoped nullifier guarding a +/// value-bearing payout lets a single deposit pay out once per epoch forever, +/// which is a live drain in a competing submission. Ours is spent once, ever. +pub fn nullifier(k: Field) -> Result { + poseidon1(k) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn f(v: u64) -> Field { + Field::from_u64(v) + } + + #[test] + fn hashing_is_deterministic() { + assert_eq!( + poseidon2(f(1), f(2)).unwrap(), + poseidon2(f(1), f(2)).unwrap() + ); + } + + #[test] + fn hashing_is_order_sensitive() { + assert_ne!( + poseidon2(f(1), f(2)).unwrap(), + poseidon2(f(2), f(1)).unwrap() + ); + } + + #[test] + fn arities_are_distinct_domains() { + // The whole point of separating by arity: padding one preimage with + // zeros must not reproduce another's digest. + let a1 = poseidon1(f(7)).unwrap(); + let a2 = poseidon2(f(7), Field::ZERO).unwrap(); + let a3 = poseidon3(f(7), Field::ZERO, Field::ZERO).unwrap(); + assert_ne!(a1, a2); + assert_ne!(a2, a3); + assert_ne!(a1, a3); + } + + #[test] + fn a_nullifier_cannot_be_confused_with_a_merkle_node() { + // Different arity, so no choice of children reproduces a nullifier by + // construction rather than by preimage hardness. + let k = f(42); + let null = nullifier(k).unwrap(); + assert_ne!(null, hash_node(Field::ZERO, k).unwrap()); + assert_ne!(null, hash_node(k, Field::ZERO).unwrap()); + } + + #[test] + fn a_commitment_is_bound_to_its_denomination() { + let (k, r) = (f(11), f(22)); + assert_ne!( + commitment(k, r, f(1)).unwrap(), + commitment(k, r, f(2)).unwrap(), + "the same note under two denominations must not share a commitment" + ); + } + + /// The anchor for the three-way parity. + /// + /// `poseidon([1, 2])` under circomlib's BN254 x5 instance is the published + /// constant + /// `7853200120776062878684798364095072458815029376092732009249414926327459813530`. + /// Pinning it here means the host, the syscall and the R1CS gadget in + /// `mirror-circuit` are each checked against an external published value + /// rather than against each other — so all three agreeing on a wrong answer + /// is not a reachable state. + #[test] + fn poseidon2_matches_the_published_circomlib_vector() { + const CIRCOMLIB_1_2: [u8; 32] = [ + 0x11, 0x5c, 0xc0, 0xf5, 0xe7, 0xd6, 0x90, 0x41, 0x3d, 0xf6, 0x4c, 0x6b, 0x96, 0x62, + 0xe9, 0xcf, 0x2a, 0x36, 0x17, 0xf2, 0x74, 0x32, 0x45, 0x51, 0x9e, 0x19, 0x60, 0x7a, + 0x44, 0x17, 0x18, 0x9a, + ]; + assert_eq!(poseidon2(f(1), f(2)).unwrap().to_bytes(), CIRCOMLIB_1_2); + } + + /// Regression vectors for the other arities we rely on. These are ours, not + /// external, and exist so that an accidental change of parameters or + /// endianness is caught rather than silently re-baselined. + #[test] + fn arity_vectors_are_stable() { + const P1_1: [u8; 32] = [ + 0x29, 0x17, 0x61, 0x00, 0xea, 0xa9, 0x62, 0xbd, 0xc1, 0xfe, 0x6c, 0x65, 0x4d, 0x6a, + 0x3c, 0x13, 0x0e, 0x96, 0xa4, 0xd1, 0x16, 0x8b, 0x33, 0x84, 0x8b, 0x89, 0x7d, 0xc5, + 0x02, 0x82, 0x01, 0x33, + ]; + const P3_123: [u8; 32] = [ + 0x0e, 0x77, 0x32, 0xd8, 0x9e, 0x69, 0x39, 0xc0, 0xff, 0x03, 0xd5, 0xe5, 0x8d, 0xab, + 0x63, 0x02, 0xf3, 0x23, 0x0e, 0x26, 0x9d, 0xc5, 0xb9, 0x68, 0xf7, 0x25, 0xdf, 0x34, + 0xab, 0x36, 0xd7, 0x32, + ]; + assert_eq!(poseidon1(f(1)).unwrap().to_bytes(), P1_1); + assert_eq!(poseidon3(f(1), f(2), f(3)).unwrap().to_bytes(), P3_123); + } +} diff --git a/crates/mirror-core/src/lib.rs b/crates/mirror-core/src/lib.rs index 383669e1..efe2c0e4 100644 --- a/crates/mirror-core/src/lib.rs +++ b/crates/mirror-core/src/lib.rs @@ -1,3 +1,33 @@ -//! Field primitives shared by the circuit, the on-chain program and the host tooling: -//! Poseidon hashing, the incremental Merkle accumulator, commitments and nullifiers. +//! Field primitives shared by the circuit, the on-chain program and the host +//! tooling: canonical BN254 scalars, Poseidon hashing with domain separation by +//! arity, the incremental Merkle accumulator, and the note model. +//! +//! This crate is linked into the on-chain program, so it carries no arkworks. +//! Field elements cross every boundary as canonical 32-byte big-endian arrays. #![forbid(unsafe_code)] + +mod field; +mod hash; +mod merkle; +mod note; + +pub use field::{Field, MODULUS_BE}; +pub use hash::{commitment, hash_node, nullifier, poseidon1, poseidon2, poseidon3, poseidon4}; +pub use merkle::{Frontier, MerkleProof, MerkleTree, TREE_DEPTH}; +pub use note::Note; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum MirrorError { + #[error("field element is not canonical: at or above the BN254 scalar modulus")] + NonCanonicalField, + #[error("field element must be exactly 32 bytes")] + BadFieldLength, + #[error("poseidon hashing failed")] + Poseidon, + #[error("the accumulator is full")] + TreeFull, + #[error("merkle path length does not match the tree depth")] + BadPathLength, + #[error("leaf index is outside the tree")] + LeafIndexOutOfRange, +} diff --git a/crates/mirror-core/src/merkle.rs b/crates/mirror-core/src/merkle.rs new file mode 100644 index 00000000..15d56a69 --- /dev/null +++ b/crates/mirror-core/src/merkle.rs @@ -0,0 +1,298 @@ +//! The incremental Poseidon Merkle accumulator. +//! +//! Two views of one structure: +//! +//! * [`Frontier`] is what the on-chain program keeps — `O(depth)` state and an +//! `O(depth)` append. It can produce the current root and nothing else. +//! * [`MerkleTree`] is the host-side full tree used to build the membership +//! paths that go into a proof. +//! +//! They must agree, and the tests here assert that they do over real insertion +//! sequences. That assertion is the point: a competing submission in this bounty +//! has no test connecting its on-chain accumulator to a proof at all, and +//! injects a fixture root instead, with a comment conceding that a real deposit +//! sequence cannot reproduce it. + +use crate::{hash_node, Field, MirrorError}; + +/// Tree depth. 2^20 leaves is roughly a million notes, which is far beyond any +/// anonymity set this protocol will realistically hold, and a depth-20 path +/// costs about 17k compute units to fold on-chain. +pub const TREE_DEPTH: usize = 20; + +/// `zeros[i]` is the root of an empty subtree of height `i`. +/// +/// Computed rather than hardcoded, because a hardcoded ladder that silently +/// disagrees with the hash function is exactly the class of bug this protocol +/// cannot survive. `Frontier::new` pays for it once. +pub fn zero_ladder() -> Result<[Field; TREE_DEPTH + 1], MirrorError> { + let mut z = [Field::ZERO; TREE_DEPTH + 1]; + for i in 1..=TREE_DEPTH { + z[i] = hash_node(z[i - 1], z[i - 1])?; + } + Ok(z) +} + +/// Insert-only accumulator holding one node per level. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Frontier { + filled: [Field; TREE_DEPTH], + zeros: [Field; TREE_DEPTH + 1], + next_index: u64, + root: Field, +} + +impl Frontier { + pub fn new() -> Result { + let zeros = zero_ladder()?; + Ok(Frontier { + filled: [Field::ZERO; TREE_DEPTH], + zeros, + next_index: 0, + root: zeros[TREE_DEPTH], + }) + } + + /// Number of leaves inserted so far. + pub fn len(&self) -> u64 { + self.next_index + } + + pub fn is_empty(&self) -> bool { + self.next_index == 0 + } + + pub fn root(&self) -> Field { + self.root + } + + /// Appends a leaf and returns its index. + pub fn insert(&mut self, leaf: Field) -> Result { + if self.next_index >= 1u64 << TREE_DEPTH { + return Err(MirrorError::TreeFull); + } + let index = self.next_index; + let mut current = leaf; + let mut path = index; + + for level in 0..TREE_DEPTH { + if path & 1 == 0 { + // We are a left child: remember this node and pair with the + // empty subtree to its right. + self.filled[level] = current; + current = hash_node(current, self.zeros[level])?; + } else { + // We are a right child: our left sibling is already known. + current = hash_node(self.filled[level], current)?; + } + path >>= 1; + } + + self.root = current; + self.next_index += 1; + Ok(index) + } +} + +/// A membership path: the sibling at each level, plus the leaf index whose bits +/// give the left/right orientation. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct MerkleProof { + pub leaf_index: u64, + pub siblings: [Field; TREE_DEPTH], +} + +impl MerkleProof { + /// Folds the path to a root. The circuit performs exactly this computation + /// under constraints; this is the native reference it must agree with. + pub fn fold(&self, leaf: Field) -> Result { + let mut current = leaf; + let mut path = self.leaf_index; + for sibling in self.siblings.iter() { + current = if path & 1 == 0 { + hash_node(current, *sibling)? + } else { + hash_node(*sibling, current)? + }; + path >>= 1; + } + Ok(current) + } +} + +/// Host-side full tree. Stores every inserted leaf so it can rebuild any path. +#[derive(Clone, Debug)] +pub struct MerkleTree { + leaves: Vec, + zeros: [Field; TREE_DEPTH + 1], +} + +impl MerkleTree { + pub fn new() -> Result { + Ok(MerkleTree { + leaves: Vec::new(), + zeros: zero_ladder()?, + }) + } + + pub fn len(&self) -> usize { + self.leaves.len() + } + + pub fn is_empty(&self) -> bool { + self.leaves.is_empty() + } + + pub fn insert(&mut self, leaf: Field) -> Result { + if self.leaves.len() as u64 >= 1u64 << TREE_DEPTH { + return Err(MirrorError::TreeFull); + } + self.leaves.push(leaf); + Ok(self.leaves.len() as u64 - 1) + } + + /// The node at `(level, index)`, materialising empty subtrees from the + /// zero ladder rather than storing them. + fn node(&self, level: usize, index: usize) -> Result { + if level == 0 { + return Ok(self.leaves.get(index).copied().unwrap_or(self.zeros[0])); + } + let span = 1usize << level; + if index * span >= self.leaves.len() { + return Ok(self.zeros[level]); + } + let left = self.node(level - 1, index * 2)?; + let right = self.node(level - 1, index * 2 + 1)?; + hash_node(left, right) + } + + pub fn root(&self) -> Result { + self.node(TREE_DEPTH, 0) + } + + /// Builds the membership path for a previously inserted leaf. + pub fn proof(&self, leaf_index: u64) -> Result { + if leaf_index as usize >= self.leaves.len() { + return Err(MirrorError::LeafIndexOutOfRange); + } + let mut siblings = [Field::ZERO; TREE_DEPTH]; + let mut index = leaf_index as usize; + for (level, slot) in siblings.iter_mut().enumerate() { + *slot = self.node(level, index ^ 1)?; + index >>= 1; + } + Ok(MerkleProof { + leaf_index, + siblings, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn f(v: u64) -> Field { + Field::from_u64(v) + } + + #[test] + fn an_empty_frontier_and_an_empty_tree_share_a_root() { + let frontier = Frontier::new().unwrap(); + let tree = MerkleTree::new().unwrap(); + assert_eq!(frontier.root(), tree.root().unwrap()); + assert_eq!(frontier.root(), zero_ladder().unwrap()[TREE_DEPTH]); + } + + #[test] + fn the_zero_ladder_climbs() { + let z = zero_ladder().unwrap(); + assert_eq!(z[0], Field::ZERO); + for i in 1..=TREE_DEPTH { + assert_eq!(z[i], hash_node(z[i - 1], z[i - 1]).unwrap()); + assert_ne!(z[i], z[i - 1], "level {i} collapsed"); + } + } + + /// The assertion a competing submission is missing entirely: the on-chain + /// accumulator and a host-generated proof must agree after a real sequence + /// of deposits, not after a fixture is injected. + #[test] + fn the_frontier_root_accepts_a_host_generated_proof() { + let mut frontier = Frontier::new().unwrap(); + let mut tree = MerkleTree::new().unwrap(); + + let leaves: Vec = (1..=9u64).map(f).collect(); + for leaf in &leaves { + let a = frontier.insert(*leaf).unwrap(); + let b = tree.insert(*leaf).unwrap(); + assert_eq!(a, b, "the two views disagree on leaf index"); + assert_eq!( + frontier.root(), + tree.root().unwrap(), + "roots diverged after {} insertions", + a + 1 + ); + } + + // Every leaf's path must fold to the accumulator's current root. + for (i, leaf) in leaves.iter().enumerate() { + let proof = tree.proof(i as u64).unwrap(); + assert_eq!( + proof.fold(*leaf).unwrap(), + frontier.root(), + "path for leaf {i} does not reach the on-chain root" + ); + } + } + + #[test] + fn a_path_folded_against_the_wrong_leaf_misses_the_root() { + let mut frontier = Frontier::new().unwrap(); + let mut tree = MerkleTree::new().unwrap(); + for v in 1..=4u64 { + frontier.insert(f(v)).unwrap(); + tree.insert(f(v)).unwrap(); + } + let proof = tree.proof(2).unwrap(); + assert_eq!(proof.fold(f(3)).unwrap(), frontier.root()); + assert_ne!( + proof.fold(f(99)).unwrap(), + frontier.root(), + "a forged leaf must not reach the root" + ); + } + + #[test] + fn insertion_order_changes_the_root() { + let mut a = Frontier::new().unwrap(); + let mut b = Frontier::new().unwrap(); + a.insert(f(1)).unwrap(); + a.insert(f(2)).unwrap(); + b.insert(f(2)).unwrap(); + b.insert(f(1)).unwrap(); + assert_ne!(a.root(), b.root()); + } + + #[test] + fn a_proof_for_an_uninserted_leaf_is_refused() { + let tree = MerkleTree::new().unwrap(); + assert!(matches!( + tree.proof(0), + Err(MirrorError::LeafIndexOutOfRange) + )); + } + + #[test] + fn the_root_advances_on_every_insertion() { + let mut frontier = Frontier::new().unwrap(); + let mut seen = vec![frontier.root()]; + for v in 1..=8u64 { + frontier.insert(f(v)).unwrap(); + let root = frontier.root(); + assert!(!seen.contains(&root), "root repeated after inserting {v}"); + seen.push(root); + } + assert_eq!(frontier.len(), 8); + } +} diff --git a/crates/mirror-core/src/note.rs b/crates/mirror-core/src/note.rs new file mode 100644 index 00000000..3a1edf8d --- /dev/null +++ b/crates/mirror-core/src/note.rs @@ -0,0 +1,76 @@ +//! The note model. +//! +//! A note is the unit of membership. Depositing escrows exactly the pool's +//! denomination and inserts `commitment(k, r, denom_tag)` as a leaf. Spending +//! proves knowledge of `(k, r)` for some leaf and publishes `nullifier(k)`, +//! which the program records permanently. +//! +//! The denomination is a pool constant rather than a field inside the note, so +//! the escrowed lamports and the hidden commitment cannot disagree. A competing +//! submission escrows an amount that is never bound to its commitment, which +//! lets a depositor of one lamport withdraw the whole pool with an entirely +//! valid proof. Here that state is not representable. + +use crate::{commitment, nullifier, Field, MirrorError}; + +/// A note's secret material. `k` is the nullifier preimage; `r` blinds the +/// commitment so that two notes with the same `k` are still distinct leaves. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Note { + pub k: Field, + pub r: Field, + pub denom_tag: Field, +} + +impl Note { + pub fn new(k: Field, r: Field, denom_tag: Field) -> Self { + Note { k, r, denom_tag } + } + + /// The Merkle leaf for this note. + pub fn commitment(&self) -> Result { + commitment(self.k, self.r, self.denom_tag) + } + + /// The nullifier published when this note is spent. + pub fn nullifier(&self) -> Result { + nullifier(self.k) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn f(v: u64) -> Field { + Field::from_u64(v) + } + + #[test] + fn blinding_makes_two_notes_with_one_nullifier_preimage_distinct_leaves() { + let a = Note::new(f(1), f(10), f(100)); + let b = Note::new(f(1), f(11), f(100)); + assert_ne!(a.commitment().unwrap(), b.commitment().unwrap()); + // ...but they share a nullifier, so only one of them is ever spendable. + // That is the depositor's own mistake to make, and it costs them a note + // rather than costing the pool anything. + assert_eq!(a.nullifier().unwrap(), b.nullifier().unwrap()); + } + + #[test] + fn the_nullifier_does_not_reveal_the_commitment() { + let n = Note::new(f(7), f(8), f(9)); + assert_ne!(n.nullifier().unwrap(), n.commitment().unwrap()); + } + + #[test] + fn a_note_is_bound_to_one_denomination() { + let a = Note::new(f(1), f(2), f(1)); + let b = Note::new(f(1), f(2), f(2)); + assert_ne!( + a.commitment().unwrap(), + b.commitment().unwrap(), + "a note minted in the 0.1 SOL pool must not be a valid leaf in the 10 SOL pool" + ); + } +} diff --git a/programs/mirror-pool/Cargo.toml b/programs/mirror-pool/Cargo.toml index 6d225972..2e44f290 100644 --- a/programs/mirror-pool/Cargo.toml +++ b/programs/mirror-pool/Cargo.toml @@ -8,8 +8,6 @@ license.workspace = true crate-type = ["cdylib", "lib"] [dependencies] +mirror-core.workspace = true solana-program.workspace = true groth16-solana.workspace = true -light-poseidon.workspace = true -ark-bn254.workspace = true -ark-ff.workspace = true From e7ea6b329e605f0ab91215941b81f0baac8cada8 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 03:31:08 -0300 Subject: [PATCH 04/83] feat(circuit): R1CS Poseidon gadget with verified three-way parity The gadget mirrors light-poseidon's permutation and reads its published round constants and MDS matrix rather than re-deriving them, so the constants have one source and the gadget cannot drift from the syscall. Verified two ways: it agrees with the native hash over random inputs at arities 1 through 4, and it reproduces circomlib's published poseidon([1,2]) vector, which mirror-core also pins. Gadget, host and syscall are therefore each checked against an external constant rather than against each other. Rejected ark-crypto-primitives' PoseidonSponge: its squeeze does not agree with circomlib's fixed-arity compression, and the one open Solana project that made it primary later migrated off it. Several published projects ship a gadget whose native and in-circuit hashes are different functions; the parity test here is what catches that. Arity-2 permutation costs 243 constraints, so a depth-20 path is under 5k. --- crates/mirror-circuit/src/lib.rs | 7 +- crates/mirror-circuit/src/poseidon_gadget.rs | 189 +++++++++++++++++++ 2 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 crates/mirror-circuit/src/poseidon_gadget.rs diff --git a/crates/mirror-circuit/src/lib.rs b/crates/mirror-circuit/src/lib.rs index 7ecde9e3..6d08c84a 100644 --- a/crates/mirror-circuit/src/lib.rs +++ b/crates/mirror-circuit/src/lib.rs @@ -1,4 +1,5 @@ -//! Groth16 membership circuit over BN254, its trusted-setup ceremony, and the -//! host-side prover. Verifying keys are exported in the byte layout the -//! on-chain `groth16-solana` verifier consumes. +//! Groth16 membership circuit over BN254, its key generation, and the host +//! prover. Host-only: never linked into the on-chain program. #![forbid(unsafe_code)] + +pub mod poseidon_gadget; diff --git a/crates/mirror-circuit/src/poseidon_gadget.rs b/crates/mirror-circuit/src/poseidon_gadget.rs new file mode 100644 index 00000000..06943311 --- /dev/null +++ b/crates/mirror-circuit/src/poseidon_gadget.rs @@ -0,0 +1,189 @@ +//! The R1CS Poseidon gadget. +//! +//! This mirrors `light_poseidon`'s permutation instruction for instruction, +//! reading its published round constants and MDS matrix rather than +//! re-deriving them. That is the whole design: the constants have exactly one +//! source, so the gadget cannot drift from the syscall. +//! +//! Two routes were rejected: +//! +//! * `ark_crypto_primitives`' `PoseidonSponge` is a sponge with rate and +//! capacity; its squeeze does not agree with circomlib's fixed-arity +//! compression. Reading `state[0]` after one permutation does agree, but that +//! is an implementation detail of the sponge rather than part of its contract. +//! The one open Solana project that made the sponge primary later migrated off +//! it. +//! * `ark_crypto_primitives::crh::poseidon::TwoToOneCRH` is not usable here for +//! the same reason. +//! +//! Several published Solana projects get this wrong in a way that only shows up +//! at proving time: their native hash and their in-circuit hash are different +//! functions, because the gadget was configured with home-rolled constants. +//! `matches_native_over_random_inputs` below is the test that would have caught +//! it. + +use ark_bn254::Fr; +use ark_ff::PrimeField; +use ark_r1cs_std::{fields::fp::FpVar, fields::FieldVar}; +use ark_relations::r1cs::SynthesisError; +use light_poseidon::{parameters::bn254_x5::get_poseidon_parameters, PoseidonParameters}; + +/// Poseidon over `inputs`, matching `light_poseidon::Poseidon::new_circom`. +/// +/// The state is `[domain_tag, inputs...]` with `domain_tag = 0`, the width is +/// `inputs.len() + 1`, and the digest is `state[0]` after the permutation. +pub fn poseidon_var(inputs: &[FpVar]) -> Result, SynthesisError> { + let width = inputs.len() + 1; + let params: PoseidonParameters = + get_poseidon_parameters::(width as u8).map_err(|_| SynthesisError::Unsatisfiable)?; + + // state = [domain_tag = 0, inputs...] + let mut state: Vec> = Vec::with_capacity(width); + state.push(FpVar::::constant(Fr::from(0u64))); + state.extend_from_slice(inputs); + + let all_rounds = params.full_rounds + params.partial_rounds; + let half_rounds = params.full_rounds / 2; + + for round in 0..half_rounds { + apply_ark(&mut state, ¶ms, round); + apply_sbox_full(&mut state, params.alpha)?; + state = apply_mds(&state, ¶ms); + } + for round in half_rounds..half_rounds + params.partial_rounds { + apply_ark(&mut state, ¶ms, round); + state[0] = pow_alpha(&state[0], params.alpha)?; + state = apply_mds(&state, ¶ms); + } + for round in half_rounds + params.partial_rounds..all_rounds { + apply_ark(&mut state, ¶ms, round); + apply_sbox_full(&mut state, params.alpha)?; + state = apply_mds(&state, ¶ms); + } + + Ok(state[0].clone()) +} + +/// Adds this round's constants. The constants are field constants, so this is +/// a linear operation and costs no constraints. +fn apply_ark(state: &mut [FpVar], params: &PoseidonParameters, round: usize) { + for (i, slot) in state.iter_mut().enumerate() { + let c = params.ark[round * params.width + i]; + *slot += FpVar::::constant(c); + } +} + +fn apply_sbox_full(state: &mut [FpVar], alpha: u64) -> Result<(), SynthesisError> { + for slot in state.iter_mut() { + *slot = pow_alpha(slot, alpha)?; + } + Ok(()) +} + +/// `x^alpha`. Alpha is 5 for every BN254 x5 instance, computed as +/// `((x^2)^2) * x` — three constraints, which is optimal for exponent 5. +fn pow_alpha(x: &FpVar, alpha: u64) -> Result, SynthesisError> { + debug_assert_eq!(alpha, 5, "the x5 parameter sets only support alpha = 5"); + let x2 = x.square()?; + let x4 = x2.square()?; + Ok(&x4 * x) +} + +/// Multiplies the state by the MDS matrix. The matrix is constant, so each +/// output is a linear combination of the inputs and costs no constraints. +fn apply_mds(state: &[FpVar], params: &PoseidonParameters) -> Vec> { + (0..params.width) + .map(|i| { + state + .iter() + .enumerate() + .fold(FpVar::::constant(Fr::from(0u64)), |acc, (j, a)| { + acc + a * FpVar::::constant(params.mds[i][j]) + }) + }) + .collect() +} + +/// Converts a canonical big-endian 32-byte element into `Fr`. +pub fn fr_from_be(bytes: &[u8; 32]) -> Fr { + Fr::from_be_bytes_mod_order(bytes) +} + +/// Converts `Fr` into canonical big-endian 32 bytes. +pub fn fr_to_be(f: &Fr) -> [u8; 32] { + use ark_ff::BigInteger; + let mut out = [0u8; 32]; + out.copy_from_slice(&f.into_bigint().to_bytes_be()); + out +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_r1cs_std::{alloc::AllocVar, R1CSVar}; + use ark_relations::r1cs::ConstraintSystem; + use ark_std::rand::{RngCore, SeedableRng}; + + fn native(inputs: &[Fr]) -> Fr { + use light_poseidon::{Poseidon, PoseidonHasher}; + let mut h = Poseidon::::new_circom(inputs.len()).unwrap(); + h.hash(inputs).unwrap() + } + + fn in_circuit(inputs: &[Fr]) -> (Fr, usize) { + let cs = ConstraintSystem::::new_ref(); + let vars: Vec> = inputs + .iter() + .map(|v| FpVar::new_witness(cs.clone(), || Ok(*v)).unwrap()) + .collect(); + let out = poseidon_var(&vars).unwrap(); + assert!(cs.is_satisfied().unwrap(), "constraint system unsatisfied"); + (out.value().unwrap(), cs.num_constraints()) + } + + /// The parity test. If this ever fails, proofs stop verifying on-chain and + /// the reason will not be obvious from the failure. + #[test] + fn matches_native_over_random_inputs() { + let mut rng = ark_std::rand::rngs::StdRng::seed_from_u64(0x6d1_7707); + for arity in 1..=4usize { + for _ in 0..8 { + let inputs: Vec = (0..arity) + .map(|_| { + let mut b = [0u8; 32]; + rng.fill_bytes(&mut b); + Fr::from_be_bytes_mod_order(&b) + }) + .collect(); + let (got, _) = in_circuit(&inputs); + assert_eq!(got, native(&inputs), "arity {arity} diverged"); + } + } + } + + /// Anchors the gadget to the same external constant `mirror-core` pins, so + /// gadget, host and syscall are each checked against circomlib rather than + /// against each other. + #[test] + fn matches_the_published_circomlib_vector() { + let (got, _) = in_circuit(&[Fr::from(1u64), Fr::from(2u64)]); + assert_eq!( + fr_to_be(&got), + [ + 0x11, 0x5c, 0xc0, 0xf5, 0xe7, 0xd6, 0x90, 0x41, 0x3d, 0xf6, 0x4c, 0x6b, 0x96, 0x62, + 0xe9, 0xcf, 0x2a, 0x36, 0x17, 0xf2, 0x74, 0x32, 0x45, 0x51, 0x9e, 0x19, 0x60, 0x7a, + 0x44, 0x17, 0x18, 0x9a, + ] + ); + } + + #[test] + fn constraint_cost_is_as_budgeted() { + let (_, n) = in_circuit(&[Fr::from(1u64), Fr::from(2u64)]); + // 8 full rounds x 3 lanes x 3 constraints + 57 partial x 3 = 243. + assert!( + (200..300).contains(&n), + "arity-2 permutation cost {n} constraints, outside the expected band" + ); + } +} From e7a2e2670abfce505fba4fafef9dfdc77de6a6cf Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 03:35:48 -0300 Subject: [PATCH 05/83] feat(circuit): membership statement, prover, and on-chain byte layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three public inputs — root, nullifier, action_binding — because on-chain verification measures at 74,179 + 5,661 per input, so each one costs ~5.7k CU. denom_tag stays a witness since Merkle membership already constrains it. The action binding is squared under constraint deliberately: a Groth16 public input participates in verification only if some constraint references it, and an unreferenced input can be dropped by the optimiser, leaving a relay free to swap the action after the fact and still verify. Setup is deterministic from a committed public seed. That trades ceremony secrecy for reproducibility in the open, and it is not a secure setup — the seed is public, so proofs are forgeable, and the multi-party path is documented separately. The competing approaches fail differently: one publishes its entropy string while gitignoring its proving key, so no third party can produce a valid proof for the deployed program at all. tests/onchain_layout.rs closes the loop against the real groth16-solana verifier rather than a stand-in, including a regression test that a non-negated proof_a is rejected. 16 tests. --- Cargo.lock | 1 + crates/mirror-circuit/Cargo.toml | 1 + crates/mirror-circuit/src/circuit.rs | 260 ++++ crates/mirror-circuit/src/lib.rs | 13 +- crates/mirror-circuit/src/prover.rs | 155 ++ crates/mirror-circuit/src/solana.rs | 165 +++ crates/mirror-circuit/tests/onchain_layout.rs | 141 ++ docs/PROVENANCE_METHOD.md | 1284 +++++++++++++++++ 8 files changed, 2018 insertions(+), 2 deletions(-) create mode 100644 crates/mirror-circuit/src/circuit.rs create mode 100644 crates/mirror-circuit/src/prover.rs create mode 100644 crates/mirror-circuit/src/solana.rs create mode 100644 crates/mirror-circuit/tests/onchain_layout.rs create mode 100644 docs/PROVENANCE_METHOD.md diff --git a/Cargo.lock b/Cargo.lock index 8fbef883..d726d0b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1065,6 +1065,7 @@ dependencies = [ "ark-serialize 0.5.0", "ark-snark", "ark-std 0.5.0", + "groth16-solana", "light-poseidon 0.4.0", "mirror-core", "solana-poseidon", diff --git a/crates/mirror-circuit/Cargo.toml b/crates/mirror-circuit/Cargo.toml index 0613a700..772dce63 100644 --- a/crates/mirror-circuit/Cargo.toml +++ b/crates/mirror-circuit/Cargo.toml @@ -23,3 +23,4 @@ thiserror.workspace = true [dev-dependencies] solana-poseidon.workspace = true +groth16-solana.workspace = true diff --git a/crates/mirror-circuit/src/circuit.rs b/crates/mirror-circuit/src/circuit.rs new file mode 100644 index 00000000..4774fffe --- /dev/null +++ b/crates/mirror-circuit/src/circuit.rs @@ -0,0 +1,260 @@ +//! The membership circuit. +//! +//! The statement: *I know `(k, r, denom_tag)` such that `H3(k, r, denom_tag)` +//! is a leaf of the tree with root `R`, my nullifier is `H1(k)`, and this proof +//! is bound to `action`.* +//! +//! Public inputs are exactly three, and that is a cost decision: on-chain +//! verification measures at `74,179 + 5,661 x N` compute units, so every input +//! is ~5.7k CU. Anything else we need to commit to is folded into the action +//! binding rather than added as a fourth input. +//! +//! | public input | why it cannot be a witness | +//! |---|---| +//! | `root` | the program checks it against its own root history | +//! | `nullifier` | the program records it to prevent replay | +//! | `action_binding` | the program recomputes it from the action it executes | +//! +//! `denom_tag` stays a witness: the Merkle membership already constrains it, +//! because a pool's tree only ever contains leaves committed at that pool's +//! denomination. + +use crate::poseidon_gadget::poseidon_var; +use ark_bn254::Fr; +use ark_r1cs_std::{ + alloc::AllocVar, + boolean::Boolean, + eq::EqGadget, + fields::{fp::FpVar, FieldVar}, + select::CondSelectGadget, +}; +use ark_relations::r1cs::{ConstraintSynthesizer, ConstraintSystemRef, SynthesisError}; +use mirror_core::TREE_DEPTH; + +#[derive(Clone)] +pub struct MembershipCircuit { + // Public. + pub root: Option, + pub nullifier: Option, + pub action_binding: Option, + // Witness. + pub k: Option, + pub r: Option, + pub denom_tag: Option, + pub leaf_index: Option, + pub siblings: Option>, +} + +impl MembershipCircuit { + /// A circuit with no assignment, for key generation. + pub fn blank() -> Self { + MembershipCircuit { + root: None, + nullifier: None, + action_binding: None, + k: None, + r: None, + denom_tag: None, + leaf_index: None, + siblings: None, + } + } +} + +impl ConstraintSynthesizer for MembershipCircuit { + fn generate_constraints(self, cs: ConstraintSystemRef) -> Result<(), SynthesisError> { + let missing = || SynthesisError::AssignmentMissing; + + // --- public inputs, in the order groth16-solana will receive them --- + let root = FpVar::new_input(cs.clone(), || self.root.ok_or_else(missing))?; + let nullifier = FpVar::new_input(cs.clone(), || self.nullifier.ok_or_else(missing))?; + let action_binding = + FpVar::new_input(cs.clone(), || self.action_binding.ok_or_else(missing))?; + + // --- witnesses --- + let k = FpVar::new_witness(cs.clone(), || self.k.ok_or_else(missing))?; + let r = FpVar::new_witness(cs.clone(), || self.r.ok_or_else(missing))?; + let denom_tag = FpVar::new_witness(cs.clone(), || self.denom_tag.ok_or_else(missing))?; + + // Path orientation bits. `Boolean::new_witness` enforces booleanity, so + // a prover cannot supply a non-binary direction and steer the fold. + let mut path_bits = Vec::with_capacity(TREE_DEPTH); + for level in 0..TREE_DEPTH { + let bit = Boolean::new_witness(cs.clone(), || { + let index = self.leaf_index.ok_or_else(missing)?; + Ok((index >> level) & 1 == 1) + })?; + path_bits.push(bit); + } + + let mut sibling_vars = Vec::with_capacity(TREE_DEPTH); + for level in 0..TREE_DEPTH { + let s = FpVar::new_witness(cs.clone(), || { + let siblings = self.siblings.as_ref().ok_or_else(missing)?; + siblings.get(level).copied().ok_or_else(missing) + })?; + sibling_vars.push(s); + } + + // --- the note commitment is the leaf --- + let leaf = poseidon_var(&[k.clone(), r, denom_tag])?; + + // --- fold the path to a root --- + // bit = 0 means we are a left child, so the sibling goes on the right. + let mut current = leaf; + for (bit, sibling) in path_bits.iter().zip(sibling_vars.iter()) { + let left = FpVar::conditionally_select(bit, sibling, ¤t)?; + let right = FpVar::conditionally_select(bit, ¤t, sibling)?; + current = poseidon_var(&[left, right])?; + } + current.enforce_equal(&root)?; + + // --- the nullifier is derived from the same k that opened the leaf --- + let computed_nullifier = poseidon_var(&[k])?; + computed_nullifier.enforce_equal(&nullifier)?; + + // --- bind the action --- + // A Groth16 public input participates in verification only if it appears + // in at least one constraint; an input referenced nowhere has an all-zero + // column and can be dropped by the optimizer, leaving it unbound. Squaring + // registers a multiplicative constraint that references it. Without this, + // a relay could swap the action after the fact and the proof would still + // verify. + let binding_squared = action_binding.square()?; + binding_squared.enforce_equal(&(&action_binding * &action_binding))?; + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::poseidon_gadget::fr_from_be; + use ark_relations::r1cs::ConstraintSystem; + use mirror_core::{Field, MerkleTree, Note}; + + fn f(v: u64) -> Field { + Field::from_u64(v) + } + + /// Builds a tree, inserts notes, and returns a fully assigned circuit for + /// the note at `target`. + fn witness_for(target: usize, count: u64) -> (MembershipCircuit, Field) { + let mut tree = MerkleTree::new().unwrap(); + let denom = f(1); + let mut notes = Vec::new(); + for i in 1..=count { + let note = Note::new(f(i * 10), f(i * 100), denom); + tree.insert(note.commitment().unwrap()).unwrap(); + notes.push(note); + } + let note = notes[target]; + let proof = tree.proof(target as u64).unwrap(); + let root = tree.root().unwrap(); + let action_binding = f(0xabcdef); + + let circuit = MembershipCircuit { + root: Some(fr_from_be(root.as_bytes())), + nullifier: Some(fr_from_be(note.nullifier().unwrap().as_bytes())), + action_binding: Some(fr_from_be(action_binding.as_bytes())), + k: Some(fr_from_be(note.k.as_bytes())), + r: Some(fr_from_be(note.r.as_bytes())), + denom_tag: Some(fr_from_be(denom.as_bytes())), + leaf_index: Some(target as u64), + siblings: Some( + proof + .siblings + .iter() + .map(|s| fr_from_be(s.as_bytes())) + .collect(), + ), + }; + (circuit, root) + } + + fn satisfied(c: MembershipCircuit) -> bool { + let cs = ConstraintSystem::::new_ref(); + c.generate_constraints(cs.clone()).unwrap(); + cs.is_satisfied().unwrap() + } + + #[test] + fn an_honest_witness_satisfies_the_circuit() { + let (circuit, _) = witness_for(3, 9); + assert!(satisfied(circuit)); + } + + #[test] + fn every_position_in_the_tree_works() { + // Left children, right children, and the boundary where the frontier + // pairs against an empty subtree. + for target in 0..7 { + let (circuit, _) = witness_for(target, 7); + assert!(satisfied(circuit), "leaf {target} failed"); + } + } + + #[test] + fn a_wrong_root_is_rejected() { + let (mut circuit, _) = witness_for(2, 5); + circuit.root = Some(Fr::from(999u64)); + assert!(!satisfied(circuit)); + } + + #[test] + fn a_wrong_nullifier_is_rejected() { + let (mut circuit, _) = witness_for(2, 5); + circuit.nullifier = Some(Fr::from(999u64)); + assert!(!satisfied(circuit)); + } + + #[test] + fn a_forged_secret_is_rejected() { + // Knowing the path is not enough; you must open the leaf. + let (mut circuit, _) = witness_for(2, 5); + circuit.k = Some(Fr::from(12345u64)); + assert!(!satisfied(circuit)); + } + + #[test] + fn a_tampered_sibling_is_rejected() { + let (mut circuit, _) = witness_for(2, 5); + let mut siblings = circuit.siblings.clone().unwrap(); + siblings[0] = Fr::from(7u64); + circuit.siblings = Some(siblings); + assert!(!satisfied(circuit)); + } + + #[test] + fn claiming_a_different_leaf_index_is_rejected() { + let (mut circuit, _) = witness_for(2, 5); + circuit.leaf_index = Some(3); + assert!(!satisfied(circuit)); + } + + #[test] + fn a_note_from_another_denomination_is_rejected() { + let (mut circuit, _) = witness_for(2, 5); + circuit.denom_tag = Some(Fr::from(2u64)); + assert!(!satisfied(circuit)); + } + + #[test] + fn the_circuit_size_is_reasonable() { + let cs = ConstraintSystem::::new_ref(); + let (circuit, _) = witness_for(1, 4); + circuit.generate_constraints(cs.clone()).unwrap(); + let n = cs.num_constraints(); + // depth 20 nodes + one commitment + one nullifier, ~243 each. + assert!( + (4_000..8_000).contains(&n), + "circuit is {n} constraints, outside the expected band" + ); + assert_eq!( + cs.num_instance_variables(), + 4, + "one plus three public inputs" + ); + } +} diff --git a/crates/mirror-circuit/src/lib.rs b/crates/mirror-circuit/src/lib.rs index 6d08c84a..6a018a7b 100644 --- a/crates/mirror-circuit/src/lib.rs +++ b/crates/mirror-circuit/src/lib.rs @@ -1,5 +1,14 @@ -//! Groth16 membership circuit over BN254, its key generation, and the host -//! prover. Host-only: never linked into the on-chain program. +//! Groth16 membership circuit over BN254, its key generation, the host prover, +//! and export into the byte layout the on-chain verifier consumes. +//! +//! Host-only: never linked into the on-chain program. #![forbid(unsafe_code)] +pub mod circuit; pub mod poseidon_gadget; +pub mod prover; +pub mod solana; + +pub use circuit::MembershipCircuit; +pub use prover::{generate, generate_reproducible, prove, Keys, SolanaProof, Witness}; +pub use solana::{vk_to_rust_source, vk_to_solana, SolanaVerifyingKey}; diff --git a/crates/mirror-circuit/src/prover.rs b/crates/mirror-circuit/src/prover.rs new file mode 100644 index 00000000..979c081f --- /dev/null +++ b/crates/mirror-circuit/src/prover.rs @@ -0,0 +1,155 @@ +//! Key generation and proving. + +use crate::{ + circuit::MembershipCircuit, + poseidon_gadget::fr_from_be, + solana::{proof_to_solana, vk_to_solana, SolanaVerifyingKey}, +}; +use ark_bn254::{Bn254, Fr}; +use ark_groth16::{Groth16, Proof, ProvingKey, VerifyingKey}; +use ark_snark::SNARK; +use ark_std::rand::{RngCore, SeedableRng}; +use mirror_core::{Field, MerkleProof, Note}; + +/// A Groth16 keypair for the membership circuit. +pub struct Keys { + pub pk: ProvingKey, + pub vk: VerifyingKey, +} + +impl Keys { + pub fn solana_vk(&self) -> SolanaVerifyingKey { + vk_to_solana(&self.vk) + } +} + +/// Generates keys from a caller-supplied RNG. +/// +/// The security of the resulting keys is exactly the security of this RNG's +/// entropy: whoever knows it can forge proofs. `generate_reproducible` below +/// makes that trade explicit rather than hiding it. +pub fn generate(rng: &mut R) -> Result { + let (pk, vk) = Groth16::::circuit_specific_setup(MembershipCircuit::blank(), rng) + .map_err(|_| SetupError::KeyGeneration)?; + Ok(Keys { pk, vk }) +} + +/// Generates keys deterministically from a public seed. +/// +/// This trades ceremony secrecy for reproducibility, deliberately and in the +/// open. Anyone can re-derive byte-identical keys from the committed seed and +/// check that the deployed program's verifying key is the one the circuit in +/// this repository produces. +/// +/// It is **not** a secure setup: the seed is public, so the toxic waste is +/// public, so proofs are forgeable. It is the honest development posture, and +/// the multi-party path that replaces it for production is a separate, +/// documented procedure. +/// +/// The competing approaches both fail differently. One publishes its entropy +/// string in the repository *and* gitignores the proving key, so the setup is +/// simultaneously insecure and unreproducible — no third party can generate any +/// valid proof for the deployed program. The other runs a real ceremony whose +/// transcript verifier checks only `delta`, so it would certify a verifying key +/// belonging to a different circuit. +pub fn generate_reproducible(seed: &[u8]) -> Result { + let mut expanded = [0u8; 32]; + for (i, slot) in expanded.iter_mut().enumerate() { + *slot = seed.get(i).copied().unwrap_or(0); + } + let mut rng = ark_std::rand::rngs::StdRng::from_seed(expanded); + generate(&mut rng) +} + +/// Everything the prover needs that is not already public. +pub struct Witness<'a> { + pub note: Note, + pub merkle_proof: &'a MerkleProof, + pub root: Field, + pub action_binding: Field, +} + +/// A proof plus the public inputs it was made against, in on-chain layout. +pub struct SolanaProof { + pub proof_a: [u8; 64], + pub proof_b: [u8; 128], + pub proof_c: [u8; 64], + /// `[root, nullifier, action_binding]`, big-endian, in circuit order. + pub public_inputs: [[u8; 32]; 3], +} + +/// Builds an assigned circuit from a witness. +fn assign(w: &Witness<'_>) -> Result<(MembershipCircuit, Field), ProveError> { + let nullifier = w.note.nullifier().map_err(|_| ProveError::Hash)?; + let circuit = MembershipCircuit { + root: Some(fr_from_be(w.root.as_bytes())), + nullifier: Some(fr_from_be(nullifier.as_bytes())), + action_binding: Some(fr_from_be(w.action_binding.as_bytes())), + k: Some(fr_from_be(w.note.k.as_bytes())), + r: Some(fr_from_be(w.note.r.as_bytes())), + denom_tag: Some(fr_from_be(w.note.denom_tag.as_bytes())), + leaf_index: Some(w.merkle_proof.leaf_index), + siblings: Some( + w.merkle_proof + .siblings + .iter() + .map(|s| fr_from_be(s.as_bytes())) + .collect(), + ), + }; + Ok((circuit, nullifier)) +} + +/// Proves membership, returning the proof in on-chain layout. +pub fn prove( + keys: &Keys, + witness: &Witness<'_>, + rng: &mut R, +) -> Result { + let (circuit, nullifier) = assign(witness)?; + let proof: Proof = + Groth16::::prove(&keys.pk, circuit, rng).map_err(|_| ProveError::Proving)?; + + // Self-check before handing the proof out. A proof that does not verify + // against its own verifying key will not verify on-chain either, and + // failing here gives a diagnosable error instead of an opaque + // ProofVerificationFailed from the syscall. + let public: Vec = vec![ + fr_from_be(witness.root.as_bytes()), + fr_from_be(nullifier.as_bytes()), + fr_from_be(witness.action_binding.as_bytes()), + ]; + let ok = + Groth16::::verify(&keys.vk, &public, &proof).map_err(|_| ProveError::Proving)?; + if !ok { + return Err(ProveError::SelfCheckFailed); + } + + let (proof_a, proof_b, proof_c) = proof_to_solana(&proof); + Ok(SolanaProof { + proof_a, + proof_b, + proof_c, + public_inputs: [ + witness.root.to_bytes(), + nullifier.to_bytes(), + witness.action_binding.to_bytes(), + ], + }) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum SetupError { + #[error("groth16 key generation failed")] + KeyGeneration, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] +pub enum ProveError { + #[error("hashing failed while assigning the witness")] + Hash, + #[error("groth16 proving failed")] + Proving, + #[error("the proof did not verify against its own verifying key")] + SelfCheckFailed, +} diff --git a/crates/mirror-circuit/src/solana.rs b/crates/mirror-circuit/src/solana.rs new file mode 100644 index 00000000..8479c112 --- /dev/null +++ b/crates/mirror-circuit/src/solana.rs @@ -0,0 +1,165 @@ +//! Conversion from arkworks 0.5 into the byte layout `groth16-solana` accepts. +//! +//! Verified by execution against the pinned versions; see +//! `docs/GROTH16_INTEGRATION.md` for the evidence and the full pitfall list. The +//! three that matter here: +//! +//! * **G2 stores `c1` before `c0`.** The syscall reads +//! `x.c1 ‖ x.c0 ‖ y.c1 ‖ y.c0`, while arkworks serialises `Fq2` as +//! `c0 ‖ c1`. Getting this backwards produces a proof that fails to verify +//! with no diagnostic. +//! * **`proof_a` must be negated.** The verifier computes +//! `e(A,B)·e(Σic,γ)·e(C,δ)·e(α,β) == 1`, which holds only for `A = −A_proof`. +//! Nothing negates it downstream. +//! * **Serialise coordinates, never the point.** arkworks packs a y-sign flag +//! into the top bit of y's most significant byte even uncompressed, on roughly +//! half of all points. Verification tolerates it, but the bytes are +//! non-canonical, which silently breaks anything that hashes or compares them +//! — including a verifying-key commitment. + +use ark_bn254::{Bn254, Fq, Fr, G1Affine, G2Affine}; +use ark_ec::AffineRepr; +use ark_ff::{BigInteger, PrimeField}; +use ark_groth16::{Proof, VerifyingKey}; + +fn fq_to_be(f: &Fq) -> [u8; 32] { + let mut out = [0u8; 32]; + out.copy_from_slice(&f.into_bigint().to_bytes_be()); + out +} + +/// A canonical field element as the on-chain verifier expects it. +pub fn fr_to_be(f: &Fr) -> [u8; 32] { + let mut out = [0u8; 32]; + out.copy_from_slice(&f.into_bigint().to_bytes_be()); + out +} + +/// G1 as `x_be ‖ y_be`. The point at infinity encodes as all zeros. +pub fn g1_to_solana(p: &G1Affine) -> [u8; 64] { + let mut out = [0u8; 64]; + if p.infinity { + return out; + } + out[..32].copy_from_slice(&fq_to_be(&p.x)); + out[32..].copy_from_slice(&fq_to_be(&p.y)); + out +} + +/// G2 as `x.c1_be ‖ x.c0_be ‖ y.c1_be ‖ y.c0_be` — note the cross order. +pub fn g2_to_solana(p: &G2Affine) -> [u8; 128] { + let mut out = [0u8; 128]; + if p.infinity { + return out; + } + out[..32].copy_from_slice(&fq_to_be(&p.x.c1)); + out[32..64].copy_from_slice(&fq_to_be(&p.x.c0)); + out[64..96].copy_from_slice(&fq_to_be(&p.y.c1)); + out[96..].copy_from_slice(&fq_to_be(&p.y.c0)); + out +} + +/// The proof in on-chain layout. `proof_a` comes back **negated**. +pub fn proof_to_solana(proof: &Proof) -> ([u8; 64], [u8; 128], [u8; 64]) { + let neg_a: G1Affine = (-proof.a.into_group()).into(); + ( + g1_to_solana(&neg_a), + g2_to_solana(&proof.b), + g1_to_solana(&proof.c), + ) +} + +/// A verifying key in the layout `groth16_solana::groth16::Groth16Verifyingkey` +/// borrows from. `ic` has one more entry than there are public inputs. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct SolanaVerifyingKey { + pub alpha_g1: [u8; 64], + pub beta_g2: [u8; 128], + pub gamma_g2: [u8; 128], + pub delta_g2: [u8; 128], + pub ic: Vec<[u8; 64]>, +} + +impl SolanaVerifyingKey { + /// Number of public inputs this key verifies. + pub fn public_input_count(&self) -> usize { + self.ic.len().saturating_sub(1) + } + + /// A stable digest over the whole key, in a fixed order. + /// + /// This is what a setup transcript must bind. A competing submission's + /// ceremony verifier checks only `delta`, so it will happily certify a + /// transcript whose verifying key belongs to an entirely different circuit. + pub fn digest_preimage(&self) -> Vec { + let mut buf = Vec::with_capacity(64 + 128 * 3 + self.ic.len() * 64 + 8); + buf.extend_from_slice(&self.alpha_g1); + buf.extend_from_slice(&self.beta_g2); + buf.extend_from_slice(&self.gamma_g2); + buf.extend_from_slice(&self.delta_g2); + buf.extend_from_slice(&(self.ic.len() as u64).to_be_bytes()); + for point in &self.ic { + buf.extend_from_slice(point); + } + buf + } +} + +pub fn vk_to_solana(vk: &VerifyingKey) -> SolanaVerifyingKey { + SolanaVerifyingKey { + alpha_g1: g1_to_solana(&vk.alpha_g1), + beta_g2: g2_to_solana(&vk.beta_g2), + gamma_g2: g2_to_solana(&vk.gamma_g2), + delta_g2: g2_to_solana(&vk.delta_g2), + ic: vk.gamma_abc_g1.iter().map(g1_to_solana).collect(), + } +} + +/// Renders the key as a Rust source constant for the on-chain program. +pub fn vk_to_rust_source(vk: &SolanaVerifyingKey) -> String { + use core::fmt::Write; + let mut s = String::new(); + let _ = writeln!( + s, + "//! Generated by `mirror setup`. Do not edit.\n\ + //!\n\ + //! Verifies {} public inputs: root, nullifier, action_binding.\n", + vk.public_input_count() + ); + let _ = writeln!(s, "use groth16_solana::groth16::Groth16Verifyingkey;\n"); + let emit = |s: &mut String, name: &str, bytes: &[u8]| { + let _ = writeln!(s, "pub const {name}: [u8; {}] = [", bytes.len()); + for chunk in bytes.chunks(12) { + let line: Vec = chunk.iter().map(|b| format!("{b}")).collect(); + let _ = writeln!(s, " {},", line.join(", ")); + } + let _ = writeln!(s, "];\n"); + }; + emit(&mut s, "VK_ALPHA_G1", &vk.alpha_g1); + emit(&mut s, "VK_BETA_G2", &vk.beta_g2); + emit(&mut s, "VK_GAMMA_G2", &vk.gamma_g2); + emit(&mut s, "VK_DELTA_G2", &vk.delta_g2); + let _ = writeln!(s, "pub const VK_IC: [[u8; 64]; {}] = [", vk.ic.len()); + for point in &vk.ic { + let _ = writeln!(s, " ["); + for chunk in point.chunks(12) { + let line: Vec = chunk.iter().map(|b| format!("{b}")).collect(); + let _ = writeln!(s, " {},", line.join(", ")); + } + let _ = writeln!(s, " ],"); + } + let _ = writeln!(s, "];\n"); + let _ = writeln!( + s, + "pub const VERIFYING_KEY: Groth16Verifyingkey<'static> = Groth16Verifyingkey {{\n\ + \x20 nr_pubinputs: {},\n\ + \x20 vk_alpha_g1: VK_ALPHA_G1,\n\ + \x20 vk_beta_g2: VK_BETA_G2,\n\ + \x20 vk_gamme_g2: VK_GAMMA_G2,\n\ + \x20 vk_delta_g2: VK_DELTA_G2,\n\ + \x20 vk_ic: &VK_IC,\n\ + }};", + vk.public_input_count() + ); + s +} diff --git a/crates/mirror-circuit/tests/onchain_layout.rs b/crates/mirror-circuit/tests/onchain_layout.rs new file mode 100644 index 00000000..f9dcd67d --- /dev/null +++ b/crates/mirror-circuit/tests/onchain_layout.rs @@ -0,0 +1,141 @@ +//! The end-to-end assertion for the proving path: a proof produced by the host +//! prover must verify under the exact verifier the on-chain program runs, using +//! the exact verifying-key bytes we will bake into it. +//! +//! `groth16-solana` compiles for the host as well as for SBF, so this exercises +//! the real verifier rather than a stand-in. If the G2 component order or the +//! `proof_a` negation were wrong, this is where it surfaces — and it surfaces +//! as a clean failure here instead of as an opaque rejection on devnet. + +use groth16_solana::groth16::{Groth16Verifier, Groth16Verifyingkey}; +use mirror_circuit::{generate_reproducible, prove, Witness}; +use mirror_core::{Field, MerkleTree, Note}; + +const SEED: &[u8] = b"mirror-pool-reproducible-dev-setup-v1"; + +struct Fixture { + keys: mirror_circuit::Keys, + tree: MerkleTree, + notes: Vec, +} + +fn fixture(count: u64) -> Fixture { + let keys = generate_reproducible(SEED).expect("key generation"); + let mut tree = MerkleTree::new().unwrap(); + let denom = Field::from_u64(1); + let mut notes = Vec::new(); + for i in 1..=count { + let note = Note::new(Field::from_u64(i * 7), Field::from_u64(i * 13), denom); + tree.insert(note.commitment().unwrap()).unwrap(); + notes.push(note); + } + Fixture { keys, tree, notes } +} + +fn verify_onchain( + svk: &mirror_circuit::SolanaVerifyingKey, + p: &mirror_circuit::SolanaProof, +) -> Result<(), groth16_solana::errors::Groth16Error> { + let vk = Groth16Verifyingkey { + nr_pubinputs: svk.public_input_count(), + vk_alpha_g1: svk.alpha_g1, + vk_beta_g2: svk.beta_g2, + vk_gamme_g2: svk.gamma_g2, + vk_delta_g2: svk.delta_g2, + vk_ic: &svk.ic, + }; + let mut verifier = + Groth16Verifier::<3>::new(&p.proof_a, &p.proof_b, &p.proof_c, &p.public_inputs, &vk)?; + verifier.verify() +} + +#[test] +fn a_host_proof_verifies_under_the_onchain_verifier() { + let f = fixture(6); + let svk = f.keys.solana_vk(); + assert_eq!(svk.public_input_count(), 3); + assert_eq!( + svk.ic.len(), + 4, + "ic carries one more entry than public inputs" + ); + + let index = 3usize; + let merkle_proof = f.tree.proof(index as u64).unwrap(); + let witness = Witness { + note: f.notes[index], + merkle_proof: &merkle_proof, + root: f.tree.root().unwrap(), + action_binding: Field::from_u64(0xfeed_beef), + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([7u8; 32]); + let proof = prove(&f.keys, &witness, &mut rng).expect("proving"); + + verify_onchain(&svk, &proof).expect("the on-chain verifier rejected an honest proof"); +} + +#[test] +fn a_tampered_public_input_is_rejected_by_the_onchain_verifier() { + let f = fixture(6); + let svk = f.keys.solana_vk(); + let merkle_proof = f.tree.proof(1).unwrap(); + let witness = Witness { + note: f.notes[1], + merkle_proof: &merkle_proof, + root: f.tree.root().unwrap(), + action_binding: Field::from_u64(1234), + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([9u8; 32]); + let mut proof = prove(&f.keys, &witness, &mut rng).expect("proving"); + + // Re-point the action binding, which is what a malicious relay would do. + // Flip a low bit so the value stays a canonical scalar and the rejection + // comes from the pairing rather than from the range check. + proof.public_inputs[2][31] ^= 1; + assert!( + verify_onchain(&svk, &proof).is_err(), + "a redirected action must not verify" + ); +} + +#[test] +fn a_non_negated_proof_a_is_rejected() { + // Guards the single easiest mistake in this integration. groth16-solana does + // not negate proof_a for you; if a future refactor drops the negation, this + // fails loudly instead of on devnet. + let f = fixture(4); + let svk = f.keys.solana_vk(); + let merkle_proof = f.tree.proof(0).unwrap(); + let witness = Witness { + note: f.notes[0], + merkle_proof: &merkle_proof, + root: f.tree.root().unwrap(), + action_binding: Field::from_u64(5), + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([3u8; 32]); + let mut proof = prove(&f.keys, &witness, &mut rng).expect("proving"); + + // Negating again returns the original, un-negated A. + let a = ark_bn254::G1Affine::new_unchecked( + ark_ff::PrimeField::from_be_bytes_mod_order(&proof.proof_a[..32]), + ark_ff::PrimeField::from_be_bytes_mod_order(&proof.proof_a[32..]), + ); + proof.proof_a = mirror_circuit::solana::g1_to_solana(&(-a)); + assert!(verify_onchain(&svk, &proof).is_err()); +} + +#[test] +fn the_setup_is_reproducible_from_its_seed() { + // Anyone can re-derive the deployed verifying key from the committed seed. + // A competing submission gitignores its proving key and publishes its + // entropy string, so its setup is both insecure and unreproducible. + let a = generate_reproducible(SEED).unwrap().solana_vk(); + let b = generate_reproducible(SEED).unwrap().solana_vk(); + assert_eq!(a, b); + let c = generate_reproducible(b"a different seed") + .unwrap() + .solana_vk(); + assert_ne!(a, c); +} + +use ark_std::rand::SeedableRng; diff --git a/docs/PROVENANCE_METHOD.md b/docs/PROVENANCE_METHOD.md new file mode 100644 index 00000000..4083c3aa --- /dev/null +++ b/docs/PROVENANCE_METHOD.md @@ -0,0 +1,1284 @@ +# Funding-Provenance Effective-k — measurement specification + +Status: specification, v1. Implement against this document. + +Every empirical number below names its source. Numbers tagged **[M 2026-07-25]** were +measured directly against Solana mainnet on that date (probe method stated inline); +numbers tagged **[V]** were verified against a primary document. Everything else is +design and must not be reported as a finding. + +--- + +## 0. Purpose and scope + +A privacy pool advertises `k` members. On a public ledger an observer can partition +those members into **funding-provenance classes** — where each member's capital came +from. If the partition is informative, the anonymity a member actually receives is +below `k`. + +This document specifies how we measure that gap: the sampling frame, the tracing +algorithm, the metric, the label resolutions, the failure accounting, and the on-disk +artifacts that let a third party reproduce every published number without network +access. + +It also specifies, in §9, what our own protocol may and may not claim about reducing +the gap — including which framings would be tautological if measured naively. + +**This crate measures. It makes no privacy claim by itself.** A number produced by +this tool describes one channel under one stated adversary at one stated label +resolution, at one pinned slot. It is not a security proof and must never be +presented as one. + +### 0.1 Conventions + +| symbol | meaning | +|---|---| +| `S` | snapshot slot. Nothing with `slot > S` is visible to any stage. | +| `K` | number of members in the measured anonymity set (after §5.2 collapse) | +| `m` | number of provenance classes observed | +| `n_c` | size of class `c`; `Σ_c n_c = K` | +| `X` | random variable: which member performed the action | +| `C` | random variable: the observed provenance class, `C = λ(X)` | +| `λ_L` | labelling function at resolution `L` (§5) | + +--- + +## 1. Adversary model + +Every reported number is meaningless without this section printed beside it. The tool +MUST emit the adversary identifier and label resolution in the same object as the +metric. + +### 1.1 `A_prov` — passive, global, retrospective, chain-only + +**Knows:** + +- The finalized Solana ledger up to snapshot slot `S`. +- The complete member set `A = {x_1 … x_K}` of the measured round. Membership is + public by construction: a deposit is an on-chain event. +- The labelling function `λ_L`, which we publish and ship. The adversary is granted + our labels exactly, no more. + +**Observes:** + +- One action `a`, known to have been produced by exactly one member. +- An action-side class `μ(a) ∈ Labels ∪ {⊥}`, computed by applying the *same* `λ_L` + to the action's funding trace. + +**Inference rule:** eliminate every `x_i` with `λ_L(x_i) ≠ μ(a)`; uniform posterior +over the survivors. Prior over `X` is uniform on `A`. + +**Explicitly denied.** The adversary has none of: + +- off-chain identity (KYC records, exchange cooperation, subpoena); +- network-layer observation (gossip, mempool, RPC-timing correlation); +- timing correlation between deposit and action; +- amount correlation between deposit and action; +- behavioural fingerprints (wallet software, compute-budget settings, fee patterns); +- active Sybil deposits into the measured pool; +- compromise of any party. + +Those are separate channels. Folding any of them in inflates the number and destroys +comparability with the published Ethereum results we benchmark against (§10.3). + +### 1.2 The two-sidedness rule + +> A partition is admissible only if the adversary can compute the same label on the +> **member side** and on the **action side**. + +This rule is load-bearing and it is the single design decision that separates this +work from the prior submissions in this bounty. + +Entropy over a partition decreases monotonically under refinement. Therefore **finer +labels always yield a lower effective-k**, and a *weaker* tracer that fails to merge +members reports a *worse* number than a strong one. A metric with that property is not +measuring the pool; it is measuring the tracer's budget. + +Raw-address partitioning violates two-sidedness: "funded by Binance hot wallet #7" is +not observable of an action whose trace reaches a *different* Binance hot wallet. The +adversary cannot exploit a distinction they cannot match on both sides. The headline +resolution is therefore the finest **two-sided** one — entity level, not address level +(§5). + +Because monotonicity runs the other way from intuition, the tool MUST NOT report a +single scalar. It reports the ladder (§5.5) and the bracket (§2.6). + +### 1.3 What the number proves + +**Proves.** Under `A_prov` at resolution `L`, at slot `S`, the measured set's +advertised `k` overstates the adversary's residual uncertainty by a factor of +`2^{H_L(C)}`. + +**Does not prove.** + +1. That any individual was deanonymized. We attempt no linkage and name no member. + The tool MUST NOT emit member addresses in any published artifact. +2. That the pool is broken. One channel, one adversary, one slot. +3. Anything composable. Effective-k values for different channels do not multiply or + add. The joint is bounded above by the minimum; computing it requires the joint + partition. +4. A durable protocol property. It is a property of the measured set at slot `S`. +5. A bound on real-world anonymity. A real adversary holds the denied channels, so + **real effective-k ≤ ours** at fixed `L`. + +**The one monotonicity we may claim:** adding channels only refines the partition, so +our number is an **upper bound on privacy against any strictly stronger adversary at +the same label resolution**. It is *not* a bound in the label-resolution direction. +State both halves; the second half is what the competing submission got backwards. + +--- + +## 2. Metrics + +### 2.1 The core identity + +`C` is a deterministic function of `X` (each member belongs to exactly one class), so +`H(C|X) = 0` and the chain rule collapses: + +``` +log2 K = H(C) + H(X|C) +``` + +which gives the definition used throughout: + +``` +H(X|C) = Σ_c (n_c/K) · log2(n_c) residual anonymity, in bits +eff_k_shannon = 2^{H(X|C)} = K / 2^{H(C)} residual anonymity, in members +``` + +`2^{H(C)}` is the *effective number of classes*. **Effective-k is nominal k divided by +the effective number of classes.** + +Two identities worth surfacing in output, because they are exact and quotable: + +``` +Shannon leakage I(X;C) = H(C) bits, exactly +min-entropy leakage = log2 m bits, exactly (Smith 2009, Thm. 1) +``` + +> **Implementation warning.** `2^{H(C)}` — entropy over the *class-size distribution* — +> is the folklore formula and it is inverted: it is maximised at total deanonymization. +> It is the **leakage**, not the anonymity. Do not compute it as the headline. A unit +> test MUST pin `H(C) + H(X|C) == log2 K`. + +### 2.2 Attribution (the widely-repeated version is wrong) + +Serjantov & Danezis, *Towards an Information Theoretic Metric for Anonymity*, PET 2002, +LNCS 2482, pp. 41–53. **[V]** Definition 2, verbatim: + +> "We define the **effective size S** of an r anonymity probability distribution U to be +> equal to the entropy of the distribution. In other words `S = − Σ_{u∈Ψ} p_u log2(p_u)` +> where `p_u = U(u, r)`." + +So `S` is **in bits**, and `p_u` ranges over **users**, not classes. The paper does +convert to a member count in its own §5 worked examples ("equivalent to a threshold mix +with `N = 2^7.127 ≈ 140` inputs") **[V]**, but that is an interpretation, not +Definition 2. The formal definition of `A = 2^{H(P)}` as a *quantity* is Andersson & +Lundin, *On the Fundamentals of Anonymity Metrics*, IFIP AICT 262, 2008, Def. 1 +("scaled anonymity set size"), who explicitly dispute the folklore attribution. + +**Required wording in any write-up:** + +> the effective anonymity set size, i.e. `2^H` where `H` is the Serjantov–Danezis +> entropy metric [S&D 2002, Def. 2]; the exponentiated form is due to Andersson & +> Lundin [2008, Def. 1] and is standard in the mixnet literature [Shmatikov & Wang, +> WPES 2006]. + +Do **not** write "Serjantov and Danezis define the effective anonymity set size as +`2^H`." + +For each observation `C = c` the posterior is uniform on class `c`, whose S&D effective +size is `log2 n_c` bits. `H(X|C)` is therefore the **expectation of the S&D effective +size over the adversary's observation**. That sentence is the whole justification and +it is unattackable; use it. + +### 2.3 The reporting ladder + +Report all of these, always. Never a scalar. + +| # | quantity | formula | interpretation | +|---|---|---|---| +| 1 | **loss factor `ρ`** | `ρ = 2^{−H(C)}` | **headline.** k-independent fraction of nominal k that survives | +| 2 | Shannon effective-k | `2^{H(X\|C)} = Π_c n_c^{n_c/K}` | membership-weighted **geometric** mean of class sizes | +| 3 | Shannon leakage | `H(C)` bits | exact | +| 4 | min-entropy effective-k | `K/m` | Bayes vulnerability; unweighted **arithmetic** mean of class sizes | +| 5 | min-entropy leakage | `log2 m` bits | exact | +| 6 | worst-case class | `min_c n_c` | **must be reported with its null distribution** (§2.5) | +| 7 | class-size CCDF | `P(my class ≤ t)`, `t ∈ {1,2,4,8,16,…}` | the distribution, not a summary | +| 8 | guessing entropy | `G(X\|C) = (1/K)·Σ_c n_c(n_c+1)/2` | expected guesses; baseline `(K+1)/2` | +| 9 | Good–Turing coverage | `Ĉ = 1 − f₁/K` | fraction of class mass observed | +| 10 | Chao1 richness | `m + f₁(f₁−1)/(2(f₂+1))` | saturation diagnostic | + +`f₁`, `f₂` = number of classes of size exactly 1, 2. + +**Ordering invariant** (verified over 200,000 random partitions, zero violations — +assert it in a property test): + +``` +min_c n_c ≤ K/m ≤ Π_c n_c^{n_c/K} ≤ K +worst min-ent Shannon advertised +``` + +Report min-entropy alongside Shannon because Shannon averages and hides the tail. The +canonical counterexample: uniform over 20 users and a 101-user distribution where the +true sender has p = 0.5 both give `H = 4.3219` bits, but the adversary succeeds one +time in twenty in the first case and one time in two in the second (Tóth, Hornák & +Vajda, NordSec 2004). + +Guessing entropy is included because it is what the canonical "advertised vs effective" +paper uses — Möser et al., *An Empirical Analysis of Traceability in the Monero +Blockchain*, PoPETs 2018(3), define effective anonymity set size as `1 + 2·Ge`. +**Indexing trap:** their `Ge = Σ_{0≤i≤M} i·p_i` starts at `i = 0` (expected *wrong* +guesses), so `Ge = G_Massey − 1`. Using 1-indexed Massey gives `M+3` where the paper +says `M+1`. Pin this in a test. + +Do **not** report Díaz et al.'s normalised degree `d = H(X)/log2 N` as a headline: it +reports ≈1.0 for a large but badly-partitioned pool, which is the opposite of what we +are measuring. Secondary metric only. + +### 2.4 `ρ` is the headline, not effective-k + +For a fixed class prior, `eff_k(k)/k` decreases toward `2^{−H(C)}` from above. Measured +by simulation over a Zipf-ish 60-class prior **[M 2026-07-25, 400 resamples per k]**: + +| nominal k | 8 | 16 | 32 | 64 | 128 | 256 | 512 | 1024 | 4096 | +|---|---|---|---|---|---|---|---|---|---| +| median `eff_k/k` | 0.177 | 0.118 | 0.085 | 0.065 | 0.055 | 0.049 | 0.046 | 0.045 | 0.043 | + +(asymptote `2^{−H(C)} = 0.0431`) + +**A measurement at small k systematically understates the steady-state loss and cannot +be extrapolated to a large pool.** `ρ` is k-independent, estimable with CIs, and +comparable across pools of different sizes. Publish `ρ` plus the `eff_k(k)` curve for +`k ∈ {8,16,32,64,128,256,512,1024}` with 5th/95th percentiles from `B = 10,000` +resamples of the measured class prior. + +### 2.5 Worst case must carry its null + +In the same simulation, `P(min_c n_c = 1) ≈ 1.00` for every `k ≤ 512` and 0.93 at +`k = 1024` **[M 2026-07-25]**. Under any heavy-tailed provenance prior *somebody is +always alone*. "Worst case is 1" is therefore not a finding about a pool; reported +bare, it is theatre. + +The tool MUST emit `worst_case_observed` together with `worst_case_null_p50` and +`worst_case_null_p05` computed by resampling the fitted class prior at the same `k`. + +### 2.6 The unresolved bracket + +Members whose trace terminates without a class are **not** a class. Merging them +assumes they are identical (charitable to the pool, raises k); splitting them into +singletons assumes they are all distinct (charitable to the adversary, lowers k). +Neither is known. + +Emit three values: + +- `eff_k_upper` — unresolved merged into one class; +- `eff_k_lower` — unresolved split into singletons; +- `eff_k_imputed` — `B = 10,000` draws imputing each unresolved member's class from the + class distribution measured on the **resolved** members. State the missing-at-random + assumption explicitly and emit an MNAR sensitivity sweep (impute instead from the + tail of the distribution) alongside. + +A point estimate without this bracket is not publishable output. + +### 2.7 Reference vectors for unit tests + +Exact values, computed at full double precision. Assert to 1e-9. + +**Vector A** — `sizes = [50, 20, 10, 10, 5, 3, 1, 1]`, `K = 100`, `m = 8` + +``` +H(C) = 2.1295115772 bits +H(X|C) = 4.5143446126 bits +H(C) + H(X|C) = 6.6438561898 == log2(100) +eff_k_shannon = 22.8535219812 +eff_k_minent = 12.5 +minent_leakage = 3.0 bits (= log2 8) +worst_case = 1 +G(X|C) = 16.18 (uniform baseline 50.5) +good_turing_C = 0.98 (f1=2, f2=0) +chao1 = 9.0 +``` + +**Vector B** — `sizes = [19, 1×11]`, `K = 30`, `m = 12` + +``` +H(C) = 2.2165365038 bits +H(X|C) = 2.6903540918 bits +eff_k_shannon = 6.4547181110 +eff_k_minent = 2.5 +minent_leakage = 3.5849625007 bits +worst_case = 1 +G(X|C) = 6.7 (uniform baseline 15.5) +good_turing_C = 0.6333333333 (f1=11, f2=0) +chao1 = 67.0 +``` + +**Vector C** — `sizes = [1×30]` (total deanonymization): `H(X|C) = 0`, +`eff_k_shannon = 1.0`, `eff_k_minent = 1.0`, `worst = 1`, `good_turing_C = 0.0`. + +**Vector D** — `sizes = [30]` (nothing partitioned): `H(C) = 0`, +`eff_k_shannon = 30.0`, `eff_k_minent = 30.0`, `worst = 30`, `G = 15.5`. + +**Vector E** — `sizes = [4, 4]`: `H(C) = 1.0`, `H(X|C) = 2.0`, `eff_k_shannon = 4.0`, +`eff_k_minent = 4.0`. + +**Negative control test (mandatory).** Inject a partial leak into a synthetic uniform +set and assert the metric drops below nominal. A metric that has never produced a bad +number for our own system has not been validated. See §9.0. + +--- + +## 3. Sampling design + +### 3.1 Two populations, two claims — never conflate + +**P1 — pool census.** All distinct depositors of pool `P` with `S₀ ≤ slot ≤ S`. If the +population is enumerable, **census it**; there is then zero sampling error and §3.4 does +not apply. + +> **Defect avoided.** The competing submission samples 30 depositors from a pool's most +> recent 300 signatures and then sets *advertised k = 30 = the sample size*. That is a +> category error: advertised k is the pool's member count, not the sample size. +> Measured **[M 2026-07-25]**: the pool in question emits ~1,000 signatures per 1.2 days, +> so a 300-signature scan covers **the most recent ≈10 hours** of pool activity. A +> 90-day census of the same pool is ≈75 pages of `getSignaturesForAddress`, ≈75,000 +> signatures — verified reachable by `before`-cursor paging **[M 2026-07-25]**. + +**P2 — population prior.** All addresses receiving a value credit ≥ threshold in window +`W`. This estimates the class prior *any* pool inherits, and yields `ρ` and the +`eff_k(k)` curve. This is the generalizable claim and it is the one to lead with. + +### 3.2 Frame construction for P2: sample slots, not consecutive blocks + +`getBlock` with `transactionDetails: "accounts"` returns exactly +`{transaction:{accountKeys,signatures}, meta:{err,fee,preBalances,postBalances, +preTokenBalances,postTokenBalances,status}}` — precisely the projection the edge +extractor needs — at **2.73 MB/block vs 6.41 MB for `"full"`**, 1,056 transactions, +1.6 s fetch **[M 2026-07-25, slot ≈435,066,350]**. + +**Procedure.** Draw `N_blocks` slots uniformly at random across `W` using the seeded +PRNG (§3.5), `getBlock` each, extract all value edges. 1,000 sampled blocks ≈ 1M +transactions ≈ 2.7 GB, ~1,000 calls. This is a genuine probability sample of chain time. + +> **Defect avoided.** The other competing submission's entire n=1,181 sample spans **9 +> consecutive slots (~3.2 s of chain time)**, containing 822 distinct destinations, and +> is presented with binomial Wilson intervals as if it were an i.i.d. population sample. +> Sampling slots uniformly costs the same and fixes it. + +### 3.3 Time stratification + +- Window `W = [S − 19,440,000, S]` ≈ **90 days = 45 epochs**. Solana fixes an epoch at + **432,000 slots** with a ~400 ms nominal slot **[V]**; Alpenglow had not reached + mainnet as of mid-2026 and slot times are unchanged. Do **not** assume 400 ms — + compute the realized mean from `blockTime` deltas over `W` and record it in the + manifest. +- **45 epoch strata** (or 90 daily strata). Allocation proportional to stratum activity; + record the allocation vector in the manifest. +- Canonical total order within a stratum: `(slot, transactionIndex, signature)`. + `getSignaturesForAddress` returns a `transactionIndex` field **[M 2026-07-25]** (not + in the RPC reference) which gives deterministic intra-slot ordering at no cost. + +### 3.4 Sample size + +`n = z²p(1−p)/w²`, finite-population correction `n/(1 + (n−1)/K)`: + +| 95% half-width | n (p=0.5) | FPC K=2,000 | FPC K=10,000 | +|---|---|---|---| +| ±10 pp | 97 | 92 | 96 | +| ±5 pp | 385 | 323 | 370 | +| **±3 pp** | **1,068** | **697** | **965** | +| ±2 pp | 2,401 | 1,092 | 1,937 | +| ±1 pp | 9,604 | 1,656 | 4,900 | + +**Baseline `n = 1,100`; stretch `n = 2,400`.** + +For scale: a published 11/30 root-hit rate carries a Wilson 95% CI of +**[21.9 %, 54.5 %] — 32.6 percentage points wide**. + +**Confidence intervals.** + +- Effective-k has no closed-form CI. Use a **stratified block bootstrap resampling + whole clusters** (funder-cluster or slot), `B = 10,000`, BCa intervals. Report the + design effect. +- **CI width must be a function of data collected, never of loops run.** A rival's + published interval `[+0.308, +0.325]` is exactly `2·1.96·√(p(1−p)/8000)` where 8,000 + is a `--n` resampling flag default — it can be made arbitrarily tight without + collecting one extra byte. Write a test that fails if the reported CI width changes + when only the resample count changes. + +**Entropy bias.** Plug-in `Ĥ` is downward-biased by ≈`(m−1)/(2n ln2)` bits. Emit +plug-in, Miller–Madow, and Chao–Shen (coverage-adjusted) side by side. Emit Good–Turing +coverage and Chao1 as saturation diagnostics; on Vector B they read 63.3 % coverage and +67 estimated classes from a sample of 30, which is how you say "this sample is nowhere +near saturating" in one number. + +### 3.5 Seeding + +``` +seed = blockhash(S) // public, fixed before we chose it — un-grindable +prng = ChaCha20(seed) +``` + +Record `S`, `blockhash(S)`, and the derivation rule in the manifest. + +### 3.6 Never filter the frame on the outcome + +> **Defect avoided.** The competing sampler discards every candidate depositor with +> ≥1,000 signatures — exactly the wallets with deep, traceable histories — and then +> reports 63 % of the remainder as untraceable. The exclusion criterion is correlated +> with the outcome. + +**Rule: nothing is excluded from the frame.** High-activity members are stratified and +reported, never dropped. If a member turns out to be a relayer or a program, that is a +finding, labelled as such, kept in the denominator. + +Related: attribute deposits by **value flow** (the account whose lamport delta went +negative into the pool), not by `accountKeys[0]`. `accountKeys[0]` is the *fee payer*; +any relayer-paid deposit is otherwise attributed to the relayer. Emit the +fee-payer/value-source disagreement rate as a statistic. + +--- + +## 4. Edge extraction and tracing + +### 4.1 Edge extraction: balance deltas, not instruction parsing + +Parsing `parsed.type == "transfer" && program == "system"` misses: + +1. programs that move lamports by direct account mutation + (`**acct.lamports.borrow_mut()`), which emit no system-transfer instruction at all; +2. `createAccount`, `createAccountWithSeed`, `transferWithSeed`, `withdrawNonceAccount`; +3. `closeAccount` lamport returns; +4. all SPL token flow. + +**Verified [M 2026-07-25]:** with `encoding: "jsonParsed"`, `accountKeys` *does* include +lookup-table-loaded addresses — a probed v0 transaction had 11 `source:"transaction"` + +9 `source:"lookupTable"` = 20 keys — and `preBalances`/`postBalances` both have length +20, aligning with the **full resolved key list**. `meta.loadedAddresses` is absent under +`jsonParsed` (folded into `accountKeys`). The RPC reference text implies otherwise; the +empirical result is authoritative. **Pin this in a test against a committed fixture.** + +``` +for each account index i in accountKeys: + Δ_i = postBalances[i] − preBalances[i] + (meta.fee if i == 0 else 0) +sources = { i : Δ_i < 0 } +sinks = { i : Δ_i > 0 } +``` + +Token flow: diff `preTokenBalances` / `postTokenBalances` by `(accountIndex, mint)`. +Each entry carries `owner`, giving the owner wallet directly from `meta`. + +Attribution within a transaction: single-source ⇒ unambiguous. Multi-source ⇒ record the +source *set* and set `ambiguous_attribution = true`. **Emit the ambiguity rate.** Use +instruction parsing only as corroboration, never as the primary extractor. + +Shape note: under `transactionDetails: "accounts"` the path is +`transaction.accountKeys`; under `"full"` it is `transaction.message.accountKeys`. + +### 4.2 The SPL blind spot — scope it explicitly + +`getSignaturesForAddress` indexes on `accountKeys` only **[V]**. A plain SPL transfer +into an existing ATA does **not** name the recipient wallet. Verified on three real +mainnet USDC transfers **[M 2026-07-25]**: the destination token account was in +`accountKeys` in 3/3 cases; the destination's **owner wallet was in 0/3**. Helius's own +documentation states it: *"Does not include associated token accounts — use +getTransactionsForAddress for complete token history."* + +**Consequence.** A wallet funded in USDC/USDT — the dominant CEX withdrawal path on +Solana — may have no signature history at its wallet address for the funding event. A +SOL-only tracer is structurally blind to it and the resulting "unresolved" is an +artifact, not a finding. + +Two supported scopes. **The manifest MUST record which one produced the headline.** + +- `scope = "sol"` — native SOL only. Cheaper; the blind spot is a stated limitation. +- `scope = "sol+spl"` — enumerate `getTokenAccountsByOwner` per address and page + `getSignaturesForAddress` on each ATA. **2.5–3× the call count** (§8). + +**Honest limitation to print in the output, either way:** a wallet that received tokens +into an ATA it later closed is invisible to `getTokenAccountsByOwner`. Bound the +residual by cross-checking against the block-sampled frame (§3.2) and report it. + +### 4.3 The edge rule: the birth edge + +**Primary rule.** For each address, follow the **oldest** value credit — the event that +created the account. Deterministic, single-valued, budget-independent, semantically +clean ("who created this wallet"), and one RPC call for most addresses. + +`getSignaturesForAddress` returns **newest-first**, and the only cursors are `before` +(page backwards) and `until` (stop early); there is no forward cursor and `limit` caps +at 1,000 **[V]**. So for an address with fewer than 1,000 lifetime signatures, the +**last element of the first page** is the birth transaction. + +> **Defect avoided.** The competing tracer scans the **six most recent** transactions of +> each address. Funding is by definition among an address's *oldest* transactions. For +> any address with more than six transactions it is reading the wrong end of the +> history, which alone manufactures "unresolved" for active wallets. This is not +> disclosed anywhere in that submission's documentation. + +**Independent corroboration of the rule.** Dune Spellbook contains +`addresses_events_solana.first_funded_by`, described in its own `schema.yml` as *"Table +showing who first funded each Solana address in SOL"* — i.e. the canonical open +implementation of this question also resolves to the *first* funding event. It sources +`system_program_call_Transfer` and is therefore **native-SOL-only**, inheriting exactly +the blind spot in §4.2. See §7.4 for how we use it (cross-check, not collection path). + +**Secondary rule, for sensitivity:** the **max-value incoming edge within `W`**. Run +both; emit the disagreement rate. + +**Do not use a set-valued class key.** A key built from "the set of hubs reached", under +a per-address funder cap and node budget, makes two identical wallets receive different +keys purely from budget-exhaustion ordering. + +### 4.4 Traversal + +``` +trace(addr, depth): + if depth > DEPTH_MAX: return Unresolved(DepthExceeded) + if terminal(addr): return Terminal(rule, id) # §4.5 + edge = birth_edge(addr) # §4.3 + if edge is None: return Unresolved(NoIncomingEdge) + if edge.value < MIN_EDGE: return Unresolved(BelowThreshold) + return trace(edge.source, depth + 1) +``` + +Single-edge following means no branching factor and no node budget — both of which were +sources of nondeterminism in prior work. Emit the **depth distribution**, not just the +mean. + +### 4.5 Terminal detection (`terminal(addr)`) + +Evaluate in order. **Record which rule fired** — the class label carries it, so every +rule can be ablated in the sensitivity table (§5.5). + +| rule | test | class label | +|---|---|---| +| **R1 program/PDA** | `getAccountInfo(addr).owner ≠ 11111111111111111111111111111111`, or `executable == true` | `program:` | +| **R2 curated anchor** | hit in the Tier-2 anchor set (§5.4) | `entity:` | +| **R3 structural cluster** | member of a fee-payer / sweep cluster of size ≥ 3 (§5.3) | `cluster:` | +| **R4 volume hub** | lifetime signatures ≥ `T_HUB` (measured, bounded — §4.6) **and** account age ≥ 30 d | `busy-unlabelled:` | +| **R5 distributor** | has funded ≥ `F_FANOUT` distinct previously-unseen addresses | `distributor:` | +| — | none fired, budget exhausted | `unresolved(reason, depth, flags)` | + +**R1 is definitional and cheap** — one `getAccountInfo` — and it is what separates +"funded by a Raydium vault" from "funded by a person". No prior submission performs it. + +**`busy-unlabelled` is not "an attributable origin". Never call it one.** + +> **Defect avoided.** In the competing tracer, `HUB_THRESHOLD == SIG_LIMIT == 1000`, so +> "reaches an attributable origin" literally means "**the address hit the RPC page +> cap**". That admits every DEX program, AMM vault, MEV bot and staking pool, and +> excludes a genuine CEX withdrawal address with 800 transactions. Our R4 decouples the +> hub test from the page cap by measuring the count with bounded paging, and requires +> corroboration from R1/R2/R3/R5 before any class is called an entity. + +### 4.6 Pagination handled honestly + +For an address with `N` lifetime signatures, reaching the oldest costs `⌈N/1000⌉` calls. +Page up to `SIG_PAGE_CAP`. If the cap is hit, the address is by construction +high-activity and becomes a **terminal hub candidate** — classified by R1/R2/R3/R5, and +labelled `busy-unlabelled` only if nothing else fires. Set `flags.page_cap_hit = true` +and record the observed signature count as a **lower bound** (`sigs ≥ 10000`), never as +an exact value. + +The page cap becomes a *classification signal with a named rule*. It never becomes a +silent "unresolved". + +Measured page-cap incidence on a naive sample of SOL-credited destinations from one +mainnet block: **4/9 resolved in a single call, 5/9 page-capped** **[M 2026-07-25, +n = 9 — small; treat as an order-of-magnitude estimate]**. Depositor wallets are +typically fresher than raw transfer destinations, so measure and report the rate for the +actual population rather than assuming. + +### 4.7 Parameters (v1) + +| parameter | value | rationale | +|---|---|---| +| `SNAPSHOT_SLOT S` | last finalized slot of a named epoch | pins the ledger; its blockhash seeds the PRNG | +| `WINDOW` | 19,440,000 slots ≈ 90 days = 45 epochs | spans regimes; ≫ one CEX operations cycle | +| `DEPTH_MAX` | 6 | observed mean depth ≈1–2; headroom is cheap under single-edge following | +| `MIN_EDGE_SOL` | 1,000,000 lamports (0.001 SOL) | above the 890,880-lamport rent-exempt floor; excludes dust-spam | +| `MIN_EDGE_SPL` | ≈$1 at a **pinned, committed** price table | prices must be pinned or results drift | +| `SIG_PAGE_CAP` | 10 pages (10,000 signatures) | bounded; flagged, never silent | +| `T_HUB` | 5,000 lifetime sigs **and** age ≥ 30 d | decoupled from the page cap; sweep 1k/5k/20k in sensitivity | +| `F_FANOUT` | 50 distinct fresh fundees | distributor detection | +| `N_BLOCKS` | 1,000 | frame construction (§3.2) | +| `B_BOOTSTRAP` | 10,000 | CIs | +| `scope` | `"sol"` \| `"sol+spl"` | §4.2 — MUST appear in manifest | + +--- + +## 5. Label tiers and multi-resolution reporting + +This section is the answer to the structural weakness in the competing metric. Specify +it fully; it is our differentiator. + +### 5.1 Four resolutions, strictly nested + +| L | resolution | derived from | what merges | +|---|---|---|---| +| **L0** | raw address | nothing | nothing. Finest ⇒ **lowest** eff-k ⇒ strongest adversary | +| **L1** | wallet-entity | ATA→owner (from `meta.*TokenBalances.owner`); co-signer clustering; R1 program/PDA detection | a wallet with its token accounts | +| **L2** | named entity | fee-payer clustering + sweep clustering + Tier-2 anchors | Binance hot wallets #3 and #7 → `Binance` | +| **L3** | entity category | hierarchy over L2 | CEX / bridge / DEX-AMM / lending / launchpad / MEV-bot / staking / unlabelled-hub / unresolved | + +Enforce the hierarchy in the type system so `L0 ⊑ L1 ⊑ L2 ⊑ L3` holds by construction. +Then + +``` +eff_k(L0) ≤ eff_k(L1) ≤ eff_k(L2) ≤ eff_k(L3) +``` + +is a theorem, not a hope, and the ladder is a genuine bracket. **Assert it at runtime**; +a violation means the hierarchy was broken and the run must fail. + +**Headline at L2** — the finest two-sided resolution (§1.2). L0/L1 are published as the +strong-adversary bound, L3 as the weak. + +### 5.2 Atoms vs crowds — the two-stage pipeline + +`2^{H(C)}` and `2^{H(X|C)}` are complements, and which is correct depends on whether +class members are mutually anonymizing: + +| the class means | members are | correct metric | +|---|---|---| +| **crowd** — distinct users sharing a funder (all withdrew from one CEX hot wallet) | mutually anonymizing | `2^{H(X\|C)}` (residual) | +| **atom** — one principal operating many addresses | **not** mutually anonymizing | `2^{H(C)}` (the class *is* the secret) | + +Pick wrong and the headline inverts. With `K = 100`: a partition of one class of 91 plus +9 singletons gives `2^{H(C)} = 1.65` and `2^{H(X|C)} = 60.6`; total deanonymization +(100 singletons) gives `2^{H(C)} = 100` and `2^{H(X|C)} = 1.0`. + +**Both kinds occur in our data.** L1 clustering produces **atoms** — those addresses +genuinely are one principal. L2 classes are **crowds** — "funded by Binance" is +thousands of distinct people. Therefore: + +1. **Collapse stage (atoms).** Apply L0→L1 clustering to reduce `K` raw member addresses + to `K'` distinct principals. Emit `K'/K` as the **Sybil deflation factor**: advertised + k is already overstated before provenance enters. All downstream metrics use `K'`. +2. **Partition stage (crowds).** Partition the `K'` principals by L2/L3 provenance class + and compute §2. + +State the assumption in one sentence in the output: *"we treat co-signing / ATA-linked +addresses as a single principal, and same-funder addresses as distinct principals; the +effect of each assumption is reported separately."* + +### 5.3 Tier 0 and Tier 1 — derived, zero license risk + +**Tier 0 — definitional, exact, from chain:** + +- ATA → owner, read directly from `meta.pre/postTokenBalances[i].owner`. +- R1 program/PDA/executable detection via `getAccountInfo`. + +**Tier 1 — structural, derived by our own committed code:** + +- **Fee-payer clustering.** On Solana `accountKeys[0]` is both fee payer and a required + signer. Exchanges pay withdrawal fees from a small set of fee-payer accounts + regardless of which hot wallet sources the value. Clustering on fee payer **collapses + an exchange's hot wallets into one entity for free, with no third-party label list.** + This is the Solana-specific unlock and it is precisely the merge the competing metric + fails to make. +- **Co-signer clustering.** A transaction with signers `{A, B}` means one party controls + both. +- **Sweep clustering.** If addresses `A_1 … A_n` each send their full balance to `H`, + they are deposit addresses of `entity(H)` — the Solana analogue of common-input- + ownership clustering. + +All three are deterministic, auditable, reproducible, and MIT because they are our code +and our derived artifact. + +### 5.4 Tier 2 — the shippable anchor set (~418 entries, all MIT-verified) + +| source | license | content | +|---|---|---| +| `solana-foundation/explorer` → `public/verified-programs.json` | MIT | **288** verified programs `{address,name,repoUrl,verifiedAt}` | +| `helius-labs/xray` → `config.ts` | MIT | 106 address→name mappings | +| `ashpoolin/gelato.sh` | MIT | 24 Solana CEX addresses | +| `0xB10C/ofac-sanctioned-digital-currency-addresses` | MIT | `sanctioned_addresses_SOL.json` | + +Ship as `labels/anchors.json`, one record per address: + +```json +{ + "address": "…", + "entity": "Binance", + "category": "cex", + "source_url": "https://github.com/…", + "source_license": "MIT", + "evidence": "fee payer for withdrawals from 5tzFki… and 9WzDXw…", + "verified_at_slot": 435071454, + "confidence": "high" +} +``` + +Ship `mirror-provenance verify-labels`, which re-checks each anchor against the chain +(e.g. that a claimed exchange fee payer still pays for withdrawals from its claimed hot +wallets). **The anchor list must be falsifiable, not asserted.** + +**OFAC.** Use the **XML** feed, not the CSV: the CSV export omits them. +`https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML` +(28.9 MB, publish date 2026-07-24, 19,254 records) contains 963 crypto addresses — XBT +524, TRX 195, ETH 96, USDT 93, LTC 14, XMR 11, **SOL 3** **[M 2026-07-25]**. Public +domain under 17 U.S.C. §105. Three addresses is not a labelling strategy; it is a +high-confidence anchor and nothing more. + +### 5.5 Tier 3 — used for sensitivity, never shipped, never in the headline + +Anything whose provenance or license we cannot stand behind. Specifically: + +- **Dune Spellbook `cex_solana.addresses`** — Business Source License 1.1 (Licensor: Dune + Analytics AS; Change Date 2027-03-03 → GPLv3+; "you may not use the Licensed Work for a + Data or Analytics Platform") **[V]**. **Not MIT-compatible; do not vendor.** The Solana + content was added after the MIT→BSL switch, so no MIT snapshot exists; after March 2027 + it is GPL, still incompatible with an MIT crate. It holds 166 rows and is noisy + (contains a hex string, a Bitcoin bech32 address, uppercase-mangled entries). Note also + that Spellbook's `labels_cex.sql` unions **only EVM chains — there is no Solana in + Dune's `labels.cex`**, so "just use Dune's labels" does not survive contact regardless + of licensing. +- **`apostleoffinance/Solana-Forensic-Analysis-Tool` → `solana_cex_labels.csv`** — MIT as + published, 99,999 valid base58 addresses of which 99,986 are `deposit_wallet`, across 19 + exchanges. Deposit-address coverage at that scale is exactly what L2 wants and no free + alternative has it. **But the provenance does not hold up:** the columns are Flipside's + `solana.core.dim_labels` schema verbatim and the row count is exactly 100,000 + header, + the signature of a `LIMIT 100000` export. A third party applied MIT to someone else's + data. Flipside is defunct (see §8.3), so practical risk is low, but "the licensor is + defunct" is not a provenance story that belongs under a headline number. +- **Commercial labels** (Arkham, Nansen, Solscan) — Solscan's ToS forbids redistribution; + the others are proprietary. `solscanofficial/labels` is first-party with 1,169 addresses + but `license: null` **[V]** — worth opening an issue requesting MIT. + +**Tier-3 output is a single sensitivity line**, e.g. *"with a 100k-row third-party +deposit-address set of uncertain provenance, the L2 effective-k moves from X to Y"* — +with the set not redistributed. The *size* of that movement is the most informative +statement we can make about how much a free-label result understates a funded adversary. + +### 5.6 The sensitivity table (required output) + +``` +resolution classes m ρ eff_k_shannon eff_k_minent worst coverage +L0 raw address … … … … … … +L1 wallet-entity … … … … … … +L2 named entity ←hl … … … … … … +L3 category … … … … … … + +ablations (at L2): + −R2 anchors … … … … … … + −R3 clusters … … … … … … + −R4 volume hubs … … … … … … + T_HUB = 1k / 5k / 20k … + Tier-3 labels on … … … … … … + scope = sol / sol+spl … +``` + +Each row also carries the §2.6 bracket. The monotonicity assertion of §5.1 applies down +the first four rows. + +--- + +## 6. Fail-closed failure accounting + +**Principle: an infrastructure limit must never read as an absence of evidence.** + +> **Defect avoided.** In the competing tracer, `sig_count()` is `.unwrap_or(0)` and +> `signatures()` is `.unwrap_or_default()`. A rate-limited call therefore makes an +> address look like it has *zero* signatures — so it is not detected as a hub and is +> admitted to the sample — and makes a traced address yield no funders, so the BFS +> terminates and the member is classified **unresolved**. Measured 429 behaviour (§8.1) +> shows that submission's 130 ms inter-call pacing is far above what the endpoint +> tolerates, so its large unresolved bucket is exactly what systematic rate limiting +> would manufacture. It does count failures and prints a warning, but the metric function +> never consults the count before reporting, and the published headline does not state it. + +### 6.1 Startup preconditions (hard) + +``` +assert getFirstAvailableBlock() == 0 +assert getTransaction(CANARY_2021_SIG) != null +record endpoint, commitment, and both results in manifest.json +``` + +Refuse to run if either fails. + +Rationale **[M 2026-07-25]**: the Solana Labs public endpoint **is** archival — +`getFirstAvailableBlock = 0`, `getBlock(50,000,000)` OK at blockTime 2020-11-19, and +`getSignaturesForAddress(addr, before=)` returns *older* 2021 signatures, +so the signature index is archival too. But third-party "public" endpoints are not: +PublicNode reports `getFirstAvailableBlock = 434,525,787` against current slot +435,068,692 — a **2.51-day window** — and `getTransaction` on a 2021 signature returns +**`{"result": null}`, not an error**. A collector pointed at it records "transaction not +found" and produces a silently truncated graph that looks like a finding. + +### 6.2 Failure taxonomy + +Every terminal state is one of these and is counted separately. There is no "other". + +| state | meaning | counts toward | +|---|---|---| +| `Terminal(rule, id)` | classified | resolved | +| `Unresolved(NoIncomingEdge)` | genuine: address has no qualifying incoming value edge | unresolved — **evidence** | +| `Unresolved(BelowThreshold)` | incoming edge below `MIN_EDGE` | unresolved — evidence | +| `Unresolved(DepthExceeded)` | hit `DEPTH_MAX` | unresolved — **budget** | +| `Unresolved(PageCapHit)` | hit `SIG_PAGE_CAP` without terminating | unresolved — budget | +| `Unresolved(RpcFailure)` | any RPC error, 429 exhaustion, or timeout | **failure — not evidence** | +| `Unresolved(SplBlindSpot)` | SOL scope, address has token accounts with inflow | unresolved — scope | + +### 6.3 Reporting rules + +1. `Unresolved(RpcFailure)` members are reported on a **separate census line** and are + excluded from the class distribution entirely. They are neither merged nor split. +2. **If the RPC failure rate exceeds 1 %, the run refuses to print a headline.** It + prints the census and exits non-zero. +3. Budget-unresolved members (`DepthExceeded`, `PageCapHit`) enter the §2.6 bracket, not + a class. +4. The manifest records total calls, failures by type, retries, and the observed 429 + rate. +5. Emit `reliable: failures == 0` as a machine-readable field, and never print a + headline number in the same object as `reliable: false`. + +--- + +## 7. Determinism, artifacts, and replay + +### 7.1 Snapshot semantics + +Everything is filtered client-side by `slot ≤ S`. `minContextSlot` is a freshness +guarantee, not a filter **[V]**, so it cannot do this. Finalized history below `S` is +immutable, so the filter is deterministic and a replay run at any later date reproduces +the same set. + +### 7.2 Record / replay cache + +- `MIRROR_RPC=record` — hits the network, appends to the store. +- `MIRROR_RPC=replay` — runs **entirely offline** from the committed store. This is the + default in CI. + +Content-addressed: `data/raw/.json`; index `data/index.jsonl` mapping +`(method, canonical_params) → sha256`. Canonical params = JSON with sorted keys, no +whitespace. + +### 7.3 On-disk format + +A `jsonParsed` `getTransaction` is 10–60 KB; 50k of them is ~1.5 GB, which does not +belong in git. **Commit the projection plus the hash of the original.** + +`data/tx/.json` (projection, ~1–3 KB): + +```json +{ + "sig": "…", "slot": 435066575, "tx_index": 530, "block_time": 1784957226, + "err": null, "fee": 5190, + "account_keys": [{"pubkey":"…","signer":true,"writable":true,"source":"transaction"}], + "pre_balances": [], "post_balances": [], + "pre_token_balances": [{"account_index":3,"mint":"…","owner":"…","amount":"…"}], + "post_token_balances": [], + "raw_sha256": "…" +} +``` + +In-repo artifacts (all small): + +| file | contents | size at n=2,000 | +|---|---|---| +| `data/frame.jsonl` | sampled slots + blockhashes | ~100 KB | +| `data/sample.jsonl` | selected addresses + selection ordinal + stratum | ~200 KB | +| `data/traces.jsonl` | per-address path, edges, terminal class, rule fired, flags | ~2 MB | +| `labels/anchors.json` | Tier-2 anchors | ~100 KB | +| `manifest.json` | §7.5 | ~4 KB | + +Bulk projections go to a tagged GitHub Release with checksums recorded in-repo. Ship +`--verify-projection`, which re-fetches originals and re-derives the projection +byte-for-byte. + +### 7.4 `first_funded_by` as cross-check, not collection path + +Dune's `addresses_events_solana.first_funded_by` precomputes our birth edge for every +Solana address. It does **not** become the collection path, for three reasons: + +1. It sources `system_program_call_Transfer` — **native SOL only** — so it inherits the + §4.2 blind spot and cannot cover the USDC/USDT funding path either. +2. Spellbook is BSL-1.1 (§5.5). Querying the hosted table is use, not redistribution, and + is fine; vendoring the model is not. +3. A headline derived from a BSL-licensed SQL model we cannot ship or audit trades away + the one thing that distinguishes this work. + +**Correct use:** derive edges ourselves from RPC, then query `first_funded_by` for the +same sampled addresses and **publish the agreement rate as an external validation +statistic**. Two independently-implemented derivations of the same edge agreeing at X % +is stronger evidence than either alone, and each disagreement is individually +inspectable — it is either our bug or theirs. `solana.account_activity` (one row per +account per transaction, with `balance_change` and `token_balance_owner`) serves the same +cross-check on the SPL side, where `first_funded_by` cannot reach. + +Dune free tier: 2,500 credits/month, 20 credits/MB export (~125 MB/month) **[V]** — enough +for a subsample cross-check, not for a full export. Join gotcha: `labels.*` stores +`address` as VARBINARY while `solana.*` uses base58 VARCHAR; use `from_base58()`. + +**Do not use BigQuery as the substrate.** `bigquery-public-data.crypto_solana_mainnet_us` +has sufficient schema but: documented gaps of 13,602 missing blocks plus 18,879 blocks +with missing or duplicate transactions; `blockchain-etl/solana-etl` last pushed +2024-09-27; recurring silent stalls (froze 2025-03-31; a 6-day lag reported 2025-11-25 +with no vendor response); and a documented case of a single Solana query billing $5,000. +If used at all, partition-filter everything and set `maximum_bytes_billed`. Google's +maintained `goog_blockchain_*` datasets cover nine chains and do not include Solana. + +### 7.5 Manifest + +```json +{ + "tool": "mirror-provenance", "version": "…", "git_commit": "…", + "snapshot_slot": 0, "snapshot_blockhash": "…", "snapshot_block_time": 0, + "window_slots": [0, 0], "realized_mean_slot_ms": 0.0, + "population": "P1_pool_census | P2_population_prior", + "pool_program": "…", + "strata": "epoch", "strata_allocation": [], + "seed_rule": "blockhash(snapshot_slot)", "seed": "…", + "scope": "sol | sol+spl", + "params": { "DEPTH_MAX": 6, "MIN_EDGE_SOL": 1000000, "SIG_PAGE_CAP": 10, + "T_HUB": 5000, "F_FANOUT": 50, "N_BLOCKS": 1000, "B_BOOTSTRAP": 10000 }, + "label_resolution_headline": "L2", + "endpoint": "…", "commitment": "finalized", + "precondition_first_available_block": 0, + "precondition_canary_ok": true, + "rpc_calls": 0, "rpc_failures": {"http_429": 0, "timeout": 0, "jsonrpc_error": 0}, + "reliable": true, + "index_sha256": "…" +} +``` + +`mirror-provenance verify` recomputes every published number from the committed store +and asserts equality. Wire it into CI. + +--- + +## 8. RPC feasibility + +### 8.1 Public endpoint — documented vs measured + +Documented **[V]**: 100 req/10 s per IP, 40 req/10 s per IP for a single method, 40 +concurrent connections, 100 MB/30 s, and *"not intended for production applications"*. + +Measured against `getSignaturesForAddress` **[M 2026-07-25]**, two independent probes: + +| pacing | result | +|---|---| +| concurrency 8, unpaced | **24/24 HTTP 429** | +| concurrency 4, unpaced | **24/24 HTTP 429** | +| concurrency 1, ~0.45 s gap (2.2 req/s offered) | 9 ok / **15 × 429** (62.5 % failure) | +| 1.0 s gap | 11 ok / 4 × 429 (27 % failure) | +| **2.0 s gap** | **15/15 ok** → 0.41 req/s clean | +| 3.0 s gap | 15/15 ok → 0.28 req/s | +| well-behaved, honoring `Retry-After`, 180 s | 99 ok / 2 × 429 → **0.55 req/s** | + +**Sustainable rate: 0.28–0.55 req/s ≈ 1,000–2,000 req/hour, per IP** — roughly 7–10× +below the documented figure. Concurrency does not help; the cap is per-IP. + +Report the measured range with the pacing method stated. Do not cite the documented +40 req/10 s, which neither probe could reproduce. + +Other findings **[M 2026-07-25]**: `getProgramAccounts` returns HTTP 403 (we do not use +it). The 100 MB/30 s data cap is not binding — at 0.55 req/s the request cap binds ~30× +earlier. + +### 8.2 Provider table + +Call budget: `scope="sol"` ≈ 17k calls at n=1,100 / 30k at n=2,000; +`scope="sol+spl"` ≈ 45k at n=1,100 / 75–90k at n=2,000 (§4.2). + +| provider | free allowance | effective rate | 45k calls | 90k calls | +|---|---|---|---|---| +| Solana Labs public | unmetered | 0.28–0.55 req/s **[M]** | ~23 h | ~45 h | +| **Helius Free** | 1M credits/mo, **1 credit/call**, archival genesis→present **[V]** | 10 rps | **1.3 h** (4.5 % quota) | 2.5 h (9 %) | +| **Alchemy Free** | 30M CU/mo, **40 CU/call** ⇒ 750k calls, full archive **[V]** | 500 CU/s ⇒ 12.5 calls/s | **1.0 h** (6 % quota) | 2.0 h (12 %) | +| QuickNode | 10M credits **one-month trial only**, 30 credits/call ⇒ 333k calls **[V]** | 15 rps | 0.8 h (14 %) | 1.7 h (27 %) | +| dRPC / Ankr keyless | **Solana blocked on free** **[M]** | — | — | — | +| Chainstack / GetBlock | archive **excluded** from free **[V]** | — | — | — | +| Blockdaemon (5 rps) / Shyft (1 rps) | — | unusable | — | — | +| Triton One | **no free tier**; $125 non-refundable minimum **[V]** | — | ~$0.90 at $10/M | — | + +**Recommendation: Alchemy Free or Helius Free for the published run** — 1–2 hours, under +12 % of a monthly allowance, no payment. Retain the public endpoint as the zero-key +reproducibility fallback and the replay cache as the zero-network one. + +### 8.3 Do not build on + +- **Flipside** — defunct. `flipsidecrypto.xyz`, `.com` and `docs.` all 301 to + `edisyl.com`; `api-v2.flipsidecrypto.xyz` fails to connect; `solana.core.dim_labels` no + longer exists; `FlipsideCrypto/solana-models` is 404. Blockchain business sold May 2026. +- **PublicNode for history** — 2.51-day window, silent nulls (§6.1). +- **BigQuery without `maximum_bytes_billed`** (§7.4). +- **SolanaFM** — `api.solana.fm` returns 502 on every endpoint. + +--- + +## 9. Honest-claims analysis + +Our protocol intends to *reduce* this attack's effect. This section states what each +candidate mechanism actually closes, what it leaves open, and the experiment that would +honestly demonstrate it. + +### 9.0 The anti-pattern, stated so it cannot be repeated + +A competing submission's effective-k harness contains, inside the metric: + +```rust +if scenario == Scenario::MirrorPool { + return vec![0usize; k]; // "Provenance broken: one indistinguishable class." +} +``` + +Everything downstream is an identity: the posterior is `vec![1.0; k]` and +`2^{H(Uniform(k))} = k`. The published table reports the favourable column as exactly +16.00 / 32.00 / 64.00 against a ragged baseline, and a unit test pins the tautology to +1e-6 as a regression guard. The data structure *contains* `k` distinct funding roots for +that scenario; the code branches on the scenario label and discards the field. + +**Three tells a reviewer can check in thirty seconds**, and which we must never produce: + +1. a literal `if scenario == X { return }` inside a metric; +2. every cell in the favourable column being an exact integer while the unfavourable + column is ragged; +3. a unit test asserting the favourable number equals nominal. + +**The structural rule that prevents it.** The adversary MUST be a pure function of the +public transcript and MUST NOT receive the scenario label: + +```rust +fn posterior(transcript: &[PublicRecord], target: ActionId) -> Vec; +``` + +No `Scenario` type may appear anywhere in the metric's call graph — enforce with a module +boundary. Every channel stays **on** and is **measured** to carry ≈0 bits, rather than +being gated off by a boolean. That converts an assumption into a testable property and +catches the case where the batch is not actually uniform. Ship a **negative control**: +inject a partial leak and assert the metric drops below nominal. + +### 9.1 (a) Provenance-homogeneous cohort formation + +*Only settle a round whose members share a provenance class.* + +**Sound: yes.** It is the only one of the three that attacks the **membership-side** +channel. If every member of a settled round shares class `c`, then `μ(a) = c` is constant +across members, `I(X; μ) = 0` within the round, and `eff_k = k_round` exactly. + +**Tautology risk: HIGH.** Measuring effective-k of homogeneous cohorts *with the labeller +that formed them* is `k_round` by definition. That is §9.0's defect written more +elegantly. + +**Leaves open:** + +1. **The round becomes the label.** Round `r` is now "the Binance round". No new + intra-round leakage, but a user acting in two rounds is correlated across time by + class. The intra-round leak is converted into an inter-round linkage. +2. **Minority members can never settle.** Under a heavy-tailed class prior most classes + are singletons — the very members the mechanism was meant to protect cannot form a + cohort at all. +3. **The guarantee is relative to a named labeller, not absolute.** If the adversary's + `λ'` is strictly finer than our `λ_form`, the cohort is not homogeneous and the + guarantee evaporates. Against a funded adversary this is a strong and probably false + assumption. + +**Honest experiment.** Using the measured mainnet class distribution, run formation policy +`π(k_min, W_max)` and report: + +- (i) effective-k under `λ_form` — stating **up front** that this is `k_round` by + construction and is not a finding; +- (ii) **the actual deliverable:** effective-k under a strictly finer, **held-out** + labeller `λ_adv` never used for formation (adversarial-refinement stress test). This + can be `< k_round` and is the real number; +- (iii) the **cost curve:** fraction of deposits settled, median and p95 wait by class, + realized `k_round` distribution vs. the unconstrained pool. + +The headline is a trade curve, not a point. Template — every letter is a placeholder to +be filled from our own measurement, never from anyone else's published figure: +*"at `k_min=8`, `W=24 h`, we settle X % of deposits at median k = Y with +adversarial-refinement effective-k of Z, versus an unconstrained pool at nominal k = A / +effective-k B."* + +### 9.2 (b) Pool-executed actions + +*The action is executed by the pool PDA, so the action's on-chain funding trace is the +pool, identical for all members.* + +**Sound: yes — the strongest of the three**, but it closes a *different* channel from the +one being measured. Fee payer, signer, and the action's immediate funding source all +become the pool PDA. `μ(a)` is constant across members, so `I(X; μ(a)) = 0` and the +backward trace of the action is dead. + +**Tautology risk: MEDIUM.** "All actions share the pool PDA as funder" *is* +near-definitional. It is not a tautology if and only if all three of these hold: + +- the tracer is our general-purpose binary run **unmodified**, same parameters; +- there is a **negative control** — the same N members performing the same action + *without* the pool, traced by the same binary, showing high recovery; +- the report is a **leakage decomposition across all channels**, not the single zero. + +A number that is zero by construction is only interesting standing next to the numbers +that are not. + +**Leaves open:** + +1. **The forward trace.** The action has a beneficiary `D`; `D`'s subsequent behaviour and + consolidation remain observable. (b) closes the backward trace and opens nothing new, + but does nothing about the fate of the proceeds. +2. **Amount and timing.** If a member deposited 7.31 SOL and the pool later sends 7.31 + SOL, the linkage is trivial. **Once (b) is in place, amount/timing is the dominant + channel** — the Tornado lesson. Fixed denominations, batching and delay are required + and the residual must be measured, not assumed. +3. **Account-key co-occurrence.** If the beneficiary ATA is member-owned, the member is + literally in `accountKeys`. Check explicitly. +4. **Who pays the fee.** If the member submits the transaction even though the PDA + executes the action, `accountKeys[0]` *is* the member — total deanonymization. This is + the number-one practical failure mode on Solana, and a rival's own sampler harvests + depositors by exactly this route. A relayer or pool-paid fees is mandatory, and the + design must make member-paid submission **impossible**, not merely discouraged. + +**Honest experiment.** Deploy, execute N actions from N distinct members, run the +unmodified tracer, and report per-channel leakage in bits with `provenance = 0.00` sitting +beside non-zero amount/timing/fee-payer figures, plus the no-pool control. + +### 9.3 (c) Pool-as-funder + +*Wallets funded by the pool all share one provenance class.* + +**Sound: partially, with a sharp ceiling.** `λ(D) = "mirror-pool"` for every pool-funded +wallet, so the provenance partition over pool-funded wallets is a single class. That is +real, and it is the primitive the other bounty repos want, since their admitted +number-one problem is the common-funder graph. + +**But there is an exact bound, and it should be stated as a theorem:** + +> Let `R` be funded by a single pool payout `π`, and let `A_π` be the set of members who +> could have caused `π`. `R`'s provenance is a deterministic function of `π`, so by the +> **data-processing inequality** the adversary's uncertainty about "who ultimately funded +> `R`" cannot exceed their uncertainty about "who caused `π`". Therefore +> +> **`eff_k(R's provenance) ≤ eff_k(the pool's membership set)`.** + +**Pool-as-funder transfers the pool's anonymity downstream; it cannot manufacture +anonymity.** That is a genuinely useful service and an honest deflation of the pitch. + +**Leaves open:** + +1. **(c) is derived from (b), not independent.** The class is "everyone the pool ever + funded" only if payouts are mutually indistinguishable. Distinct amounts and slots + sub-partition it; without fixed denominations, batching and delay, the class collapses + to one member per payout. +2. **Taint concentration.** One shared anonymity class is also one shared *taint* class. + Pool-funded wallets inherit "funded by a mixer", which many exchanges flag. An + adversary can deliberately deposit and withdraw to make the class conspicuous. This + trades deanonymization risk for compliance risk and must be stated. +3. **Membership becomes maximally visible.** "Funded by the privacy pool" is itself highly + informative — it says the recipient wanted privacy. (c) maximizes anonymity *within* + the pool population while maximizing the visibility of *belonging to* it. Against the + question "is this user privacy-seeking?" it increases leakage to 1 bit while decreasing + it on "which user". State this ourselves before a reviewer does. + +**Tautology risk: MEDIUM-HIGH** if measured as "all pool-funded wallets have class = +pool" (definitional). Not a tautology if measured as: + +- **the ceiling test** — does realized class size equal the pool's effective-k, or does it + collapse toward 1 under amount/timing sub-partitioning? +- **a cross-repo A/B** — offer it as an API to the other two bounty repos, run our tracer + on their decoy sets *before* (common funder) and *after* (pool-funded), and report both + effective-k values from the same binary. Hard to fake, easy to check, and it makes + their data our evidence. + +--- + +## 10. Implementation + +### 10.1 Build order + +Target crate: `crates/mirror-provenance`. + +1. `metrics.rs` — §2 as pure functions over class-size multisets, with the §2.7 reference + vectors, the ordering invariant, and the negative control. **No RPC, no scenario + parameter, no I/O.** This is the file a reviewer reads first; it must be obviously + honest. +2. `edges.rs` — §4.1 balance-delta extractor, with a committed mainnet fixture pinning the + verified `accountKeys` / `preBalances` alignment for a v0 lookup-table transaction. +3. `rpc.rs` — §6.1 preconditions, §7.2 record/replay, `slot ≤ S` filter, `before`-cursor + paging with explicit truncation flags, §6.2 failure taxonomy, fail-closed reporting. +4. `labels.rs` — §5 ladder with nesting enforced by types, Tier-0/1 derivation, + `labels/anchors.json`, `verify-labels`. +5. `trace.rs` — §4.3–4.6 birth-edge traversal with the §4.7 parameters. +6. `sample.rs` — §3 block-sampled frame, epoch stratification, blockhash seed, stratified + cluster bootstrap. +7. `mirror-provenance verify` in CI, running in `replay` mode. + +### 10.2 Publication order + +1. `ρ` — the loss factor +2. the `eff_k(k)` curve with CIs +3. the §5.6 label-resolution ladder with the §2.6 bracket +4. the class-size CCDF +5. the unresolved and failure census +6. the adversary model +7. the limitations + +**The differentiator is not a smaller number than anyone else's. It is that a reviewer +can tell which direction our error goes.** + +### 10.3 Prior art — what we may and may not claim + +The funding-provenance channel is **not novel**. Wang et al., *On How Zero-Knowledge Proof +Blockchain Mixers Improve, and Worsen User Privacy*, WWW 2023, Heuristic H4 "Intermediary +Deposit Address" is this heuristic, published for Tornado Cash: + +> "given two addresses `d⁽¹⁾` and `d⁽²⁾`, if all `d⁽¹⁾`'s coins are transferred from +> `d⁽²⁾` and `d⁽²⁾` is a user account, then `Link(d⁽¹⁾, d⁽²⁾) = 1`." + +Measured effect on TC 0.1 ETH (|OAS| = 11,941): H4 alone −4.20 %; all five heuristics +combined −30.68 %. Their metric is Bayes vulnerability `Adv = 1/|SAS|` — identical to our +min-entropy rung. + +**Claim exactly three things, all defensible:** + +1. **First on Solana.** DBLP's full index returns six Solana blockchain papers (phishing, + rug detection, transaction failure, Jito MEV, NFT ecosystem, SolRPDS) — **none** on + privacy, anonymity, deanonymization, address clustering, mixers or shielded pools. + arXiv full-text agrees. Elusiv sunset 2024-02-29 (team → Arcium, general MPC) and was + never measured; Light Protocol pivoted to ZK Compression; Privacy Cash (launched + 2025-08-27) is the most-used Solana ZK mixer and has never been measured. +2. **Entity-level label ladder with reported sensitivity**, where all prior work — Wang et + al. included — fixes one resolution. +3. **`ρ = 2^{−H(C)}` as a k-independent headline**, where all prior work reports a + percentage reduction for one pool at one size. + +Useful framing: SPL Token-2022 confidential transfers hide *amounts and balances only* — +sender and receiver addresses stay public — so by construction the advertised anonymity +set is exactly 1. Solana's flagship privacy primitive has no anonymity set, and the one +protocol class where an advertised-vs-effective gap could exist has never been studied. + +**Comparanda for our result:** Tutela (arXiv:2201.06811) −37 % ± 15 %; Wang et al. −27.34 % +(ETH) / −46.02 % (BSC); Béres et al. (IEEE DAPPS 2021) anonymity set 400 → ~12 under a +one-day timing assumption; Kappos et al. (USENIX Security 2018, Zcash) −69.1 % **of value, +not of members**; Möser et al. (PoPETs 2018, Monero) ring size 11 → effective 1.16–1.80. + +**Do not cite arXiv:2510.09433** (Cristodaro, Kraner & Tessone, Tornado Cash cross-chain +clustering). It was **withdrawn** at v3 on 2025-11-18: *"This paper has been withdrawn by +the author due to mistakes in the references"* **[V]**. Its numbers circulate widely in +search results and a competing submission relies on it. + +**Terminology.** "Provenance class" is not standard. Define it once against the +established vocabulary: *"we partition the anonymity set into* **provenance classes**, +*the equivalence classes induced by the common-funder relation; these correspond to* +clusters *in the address-clustering literature (Meiklejohn et al., IMC 2013; Victor, FC +2020) and to the blocks of a* partition gain function *in the QIF sense (Alvim et al., +CSF 2012, §III-B-2)."* + +Attribution note: common-input-ownership is **not** Meiklejohn's — they explicitly +disclaim it. The chain is Nakamoto §10 (observation) → Reid & Harrigan 2011 (graph +contraction) → Androulaki et al., FC 2013 (change addresses) → Meiklejohn et al., IMC 2013 +(refined change heuristic, active re-identification, **peel chain** — that coinage is +theirs). For the account model the right citation is **Victor, FC 2020, deposit-address +reuse**, the direct ancestor of what we are doing. + +### 10.4 Structural template + +Huseynov, Shahzaib, Seres & Tapolcai, *A Tattered Cloak of Invisibility: Measuring +Anonymity Loss in Railgun on Ethereum*, arXiv:2606.25926 (June 2026) is the closest +existing work in form: it uses "nominal vs effective", cites both PET 2002 papers, defines +an optimistic upper bound `log2|D(w)|` beside the measured value, and reports a **3.42-bit +median anonymity loss** — a distribution, in bits, not a scalar. Imitate its structure. From 830eea33a46123955711b7a350c47ccebde1ea16 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 03:40:09 -0300 Subject: [PATCH 06/83] fix: implement the action binding the docs promised; scope the reproducibility claim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audit of the work so far turned up three defects of exactly the kind this submission criticises in others: - hash.rs documented an arity-4 action binding that did not exist, and poseidon4 was exported but never called. The binding is a real requirement — the program recomputes it from the action it executes — so it now lives in mirror-core, shared by host and program so the two cannot drift. A 32-byte pubkey exceeds the BN254 scalar field, so it is split into two 16-byte halves rather than reduced; reducing would let two beneficiaries share one binding. - The circuit computed the binding's square twice, spending three constraints where one suffices. Reduced to one, and the integration test that tampers with that public input still rejects, which is what proves the input is genuinely bound rather than merely present. - The reproducible-setup docstring overclaimed. StdRng is not guaranteed portable across rand releases and arkworks' randomness consumption order is an implementation detail, so reproducibility is scoped to the committed lockfile. Said so. --- crates/mirror-circuit/src/circuit.rs | 23 ++++--- crates/mirror-circuit/src/prover.rs | 8 +++ crates/mirror-core/src/hash.rs | 98 +++++++++++++++++++++++++++- crates/mirror-core/src/lib.rs | 4 +- 4 files changed, 123 insertions(+), 10 deletions(-) diff --git a/crates/mirror-circuit/src/circuit.rs b/crates/mirror-circuit/src/circuit.rs index 4774fffe..d1016225 100644 --- a/crates/mirror-circuit/src/circuit.rs +++ b/crates/mirror-circuit/src/circuit.rs @@ -114,14 +114,21 @@ impl ConstraintSynthesizer for MembershipCircuit { computed_nullifier.enforce_equal(&nullifier)?; // --- bind the action --- - // A Groth16 public input participates in verification only if it appears - // in at least one constraint; an input referenced nowhere has an all-zero - // column and can be dropped by the optimizer, leaving it unbound. Squaring - // registers a multiplicative constraint that references it. Without this, - // a relay could swap the action after the fact and the proof would still - // verify. - let binding_squared = action_binding.square()?; - binding_squared.enforce_equal(&(&action_binding * &action_binding))?; + // A Groth16 public input participates in verification only through the + // R1CS columns that reference it. An input used in no constraint has an + // all-zero column, so its `gamma_abc` term is the identity and *any* + // value satisfies the verification equation — the input is present but + // unbound. A relay could then swap the action after proving and the + // proof would still verify. + // + // One multiplicative constraint is enough to give it a non-zero column. + // The product itself is never used; emitting the constraint is the point. + // + // This is load-bearing rather than defensive, and it is checked rather + // than assumed: `tests/onchain_layout.rs` tampers with this exact public + // input and asserts the real on-chain verifier rejects the result. If + // this line were removed, that test would fail. + let _binding_is_referenced = action_binding.square()?; Ok(()) } diff --git a/crates/mirror-circuit/src/prover.rs b/crates/mirror-circuit/src/prover.rs index 979c081f..e3a42b2e 100644 --- a/crates/mirror-circuit/src/prover.rs +++ b/crates/mirror-circuit/src/prover.rs @@ -46,6 +46,14 @@ pub fn generate(rng: &mut R) -> Result Result { poseidon1(k) } +/// Action binding: `H4(selector, beneficiary_hi, beneficiary_lo, relay_fee)`. +/// +/// This is the value the circuit takes as its third public input and the value +/// the program recomputes from the action it is about to execute. It lives here, +/// shared by both, so the two cannot drift apart. +/// +/// It covers every economically meaningful field of a spend. The payout is the +/// pool denomination minus `relay_fee`, so binding the beneficiary and the fee +/// binds the amount too: a relay can neither redirect the payout nor inflate its +/// own cut, because either change produces a different binding and the proof +/// stops verifying. +/// +/// A 32-byte public key does not fit in a BN254 scalar — the field is ~254 bits +/// — so the key is split into two 16-byte halves. Each half is well under the +/// modulus, the split is injective, and no reduction ever happens. Reducing a +/// full key instead would let two distinct beneficiaries share one binding. +pub fn action_binding( + selector: u64, + beneficiary: &[u8; 32], + relay_fee: u64, +) -> Result { + let mut hi = [0u8; 32]; + hi[16..].copy_from_slice(&beneficiary[..16]); + let mut lo = [0u8; 32]; + lo[16..].copy_from_slice(&beneficiary[16..]); + + poseidon4( + Field::from_u64(selector), + // Both halves are < 2^128, so these constructions cannot fail. + Field::from_bytes(hi)?, + Field::from_bytes(lo)?, + Field::from_u64(relay_fee), + ) +} + #[cfg(test)] mod tests { use super::*; @@ -140,6 +175,67 @@ mod tests { ); } + #[test] + fn the_action_binding_covers_every_field_a_relay_could_change() { + let bob = [7u8; 32]; + let base = action_binding(1, &bob, 5_000).unwrap(); + + let mut carol = [7u8; 32]; + carol[31] = 8; + assert_ne!( + base, + action_binding(1, &carol, 5_000).unwrap(), + "a redirected beneficiary must change the binding" + ); + assert_ne!( + base, + action_binding(1, &bob, 6_000).unwrap(), + "an inflated relay fee must change the binding" + ); + assert_ne!( + base, + action_binding(2, &bob, 5_000).unwrap(), + "a different action must change the binding" + ); + } + + #[test] + fn the_beneficiary_split_is_injective_across_the_halfway_boundary() { + // Two keys differing only at byte 15 and two differing only at byte 16 + // land in different halves. If the split dropped or overlapped a byte, + // one of these pairs would collide. + let base = [0u8; 32]; + let mut a = base; + a[15] = 1; + let mut b = base; + b[16] = 1; + let zero = action_binding(0, &base, 0).unwrap(); + let ha = action_binding(0, &a, 0).unwrap(); + let hb = action_binding(0, &b, 0).unwrap(); + assert_ne!(zero, ha); + assert_ne!(zero, hb); + assert_ne!(ha, hb); + } + + #[test] + fn a_full_width_key_binds_without_reduction() { + // An all-0xff key exceeds the BN254 modulus. Reducing it whole would be + // a silent collision surface; the halves keep it exact. + let max = [0xffu8; 32]; + assert!( + Field::from_bytes(max).is_err(), + "precondition: not canonical" + ); + assert!(action_binding(0, &max, 0).is_ok()); + + let mut near = [0xffu8; 32]; + near[0] = 0xfe; + assert_ne!( + action_binding(0, &max, 0).unwrap(), + action_binding(0, &near, 0).unwrap() + ); + } + /// The anchor for the three-way parity. /// /// `poseidon([1, 2])` under circomlib's BN254 x5 instance is the published diff --git a/crates/mirror-core/src/lib.rs b/crates/mirror-core/src/lib.rs index efe2c0e4..f318d9a5 100644 --- a/crates/mirror-core/src/lib.rs +++ b/crates/mirror-core/src/lib.rs @@ -12,7 +12,9 @@ mod merkle; mod note; pub use field::{Field, MODULUS_BE}; -pub use hash::{commitment, hash_node, nullifier, poseidon1, poseidon2, poseidon3, poseidon4}; +pub use hash::{ + action_binding, commitment, hash_node, nullifier, poseidon1, poseidon2, poseidon3, poseidon4, +}; pub use merkle::{Frontier, MerkleProof, MerkleTree, TREE_DEPTH}; pub use note::Note; From 0747dbf986884715897f9fbe7866c0eda3b77480 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 03:46:34 -0300 Subject: [PATCH 07/83] perf(core): bake the zero ladder, guarded by a drift test The on-chain deposit path needs an empty-subtree root at every level it climbs, and deriving the ladder costs roughly 17k compute units. Baking it as a constant makes that free. A hardcoded ladder that disagrees with the hash function is a nasty failure: deposits keep succeeding while no proof can ever verify, because every root is computed against empty subtrees that no longer exist. So the derivation stays as the reference implementation and a test asserts the two agree level by level. A second test asserts every baked entry is a canonical field element, since a non-canonical constant would only fail at runtime, on-chain, with no diagnostic. --- crates/mirror-core/src/lib.rs | 4 +- crates/mirror-core/src/merkle.rs | 169 +++++++++++++++++++++++++++++-- 2 files changed, 166 insertions(+), 7 deletions(-) diff --git a/crates/mirror-core/src/lib.rs b/crates/mirror-core/src/lib.rs index f318d9a5..bc3231b0 100644 --- a/crates/mirror-core/src/lib.rs +++ b/crates/mirror-core/src/lib.rs @@ -15,7 +15,9 @@ pub use field::{Field, MODULUS_BE}; pub use hash::{ action_binding, commitment, hash_node, nullifier, poseidon1, poseidon2, poseidon3, poseidon4, }; -pub use merkle::{Frontier, MerkleProof, MerkleTree, TREE_DEPTH}; +pub use merkle::{ + baked_ladder, zero_ladder, Frontier, MerkleProof, MerkleTree, TREE_DEPTH, ZERO_LADDER, +}; pub use note::Note; #[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)] diff --git a/crates/mirror-core/src/merkle.rs b/crates/mirror-core/src/merkle.rs index 15d56a69..380cf97e 100644 --- a/crates/mirror-core/src/merkle.rs +++ b/crates/mirror-core/src/merkle.rs @@ -20,11 +20,128 @@ use crate::{hash_node, Field, MirrorError}; /// costs about 17k compute units to fold on-chain. pub const TREE_DEPTH: usize = 20; -/// `zeros[i]` is the root of an empty subtree of height `i`. +/// Precomputed empty-subtree roots: `ZERO_LADDER[i]` is the root of an empty +/// subtree of height `i`. /// -/// Computed rather than hardcoded, because a hardcoded ladder that silently -/// disagrees with the hash function is exactly the class of bug this protocol -/// cannot survive. `Frontier::new` pays for it once. +/// Baked rather than computed because the on-chain program needs the ladder on +/// every deposit, and recomputing it would cost about 17k compute units per +/// instruction. `the_baked_ladder_matches_the_computed_one` asserts the two +/// agree, so a change to the hash function cannot silently desynchronise them. +pub const ZERO_LADDER: [[u8; 32]; TREE_DEPTH + 1] = [ + // level 0 + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, + ], + // level 1 + [ + 32, 152, 245, 251, 158, 35, 158, 171, 60, 234, 195, 242, 123, 129, 228, 129, 220, 49, 36, + 213, 95, 254, 213, 35, 168, 57, 238, 132, 70, 182, 72, 100, + ], + // level 2 + [ + 16, 105, 103, 61, 205, 177, 34, 99, 223, 48, 26, 111, 245, 132, 167, 236, 38, 26, 68, 203, + 157, 198, 141, 240, 103, 164, 119, 68, 96, 177, 241, 225, + ], + // level 3 + [ + 24, 244, 51, 49, 83, 126, 226, 175, 46, 61, 117, 141, 80, 247, 33, 6, 70, 124, 110, 234, + 80, 55, 29, 213, 40, 213, 126, 178, 184, 86, 210, 56, + ], + // level 4 + [ + 7, 249, 216, 55, 203, 23, 176, 211, 99, 32, 255, 233, 59, 165, 35, 69, 241, 183, 40, 87, + 26, 86, 130, 101, 202, 172, 151, 85, 157, 188, 149, 42, + ], + // level 5 + [ + 43, 148, 207, 94, 135, 70, 179, 245, 201, 99, 31, 76, 93, 243, 41, 7, 166, 153, 197, 140, + 148, 178, 173, 77, 123, 92, 236, 22, 57, 24, 63, 85, + ], + // level 6 + [ + 45, 238, 147, 197, 166, 102, 69, 150, 70, 234, 125, 34, 204, 169, 225, 188, 254, 215, 30, + 105, 81, 185, 83, 97, 29, 17, 221, 163, 46, 160, 157, 120, + ], + // level 7 + [ + 7, 130, 149, 229, 162, 43, 132, 233, 130, 207, 96, 30, 182, 57, 89, 123, 139, 5, 21, 168, + 140, 181, 172, 127, 168, 164, 170, 190, 60, 135, 52, 157, + ], + // level 8 + [ + 47, 165, 229, 241, 143, 96, 39, 166, 80, 27, 236, 134, 69, 100, 71, 42, 97, 107, 46, 39, + 74, 65, 33, 26, 68, 76, 190, 58, 153, 243, 204, 97, + ], + // level 9 + [ + 14, 136, 67, 118, 208, 216, 253, 33, 236, 183, 128, 56, 158, 148, 31, 102, 228, 94, 122, + 204, 227, 226, 40, 171, 62, 33, 86, 166, 20, 252, 215, 71, + ], + // level 10 + [ + 27, 114, 1, 218, 114, 73, 79, 30, 40, 113, 122, 209, 165, 46, 180, 105, 249, 88, 146, 249, + 87, 113, 53, 51, 222, 97, 117, 229, 218, 25, 10, 242, + ], + // level 11 + [ + 31, 141, 136, 34, 114, 94, 54, 56, 82, 0, 192, 178, 1, 36, 152, 25, 166, 230, 225, 228, + 101, 8, 8, 181, 190, 188, 107, 250, 206, 125, 118, 54, + ], + // level 12 + [ + 44, 93, 130, 246, 108, 145, 75, 175, 185, 112, 21, 137, 186, 140, 252, 251, 97, 98, 176, + 161, 42, 207, 136, 168, 208, 135, 154, 4, 113, 181, 248, 90, + ], + // level 13 + [ + 20, 197, 65, 72, 160, 148, 11, 184, 32, 149, 127, 90, 223, 63, 161, 19, 78, 245, 196, 170, + 161, 19, 244, 100, 100, 88, 242, 112, 224, 191, 191, 208, + ], + // level 14 + [ + 25, 13, 51, 177, 47, 152, 111, 150, 30, 16, 192, 238, 68, 216, 185, 175, 17, 190, 37, 88, + 140, 173, 137, 212, 22, 17, 142, 75, 244, 235, 232, 12, + ], + // level 15 + [ + 34, 249, 138, 169, 206, 112, 65, 82, 172, 23, 53, 73, 20, 173, 115, 237, 17, 103, 174, 101, + 150, 175, 81, 10, 165, 179, 100, 147, 37, 224, 108, 146, + ], + // level 16 + [ + 42, 124, 124, 155, 108, 229, 136, 11, 159, 111, 34, 141, 114, 191, 106, 87, 90, 82, 111, + 41, 198, 110, 204, 238, 248, 183, 83, 211, 139, 186, 115, 35, + ], + // level 17 + [ + 46, 129, 134, 229, 88, 105, 142, 193, 198, 122, 249, 193, 77, 70, 63, 252, 71, 0, 67, 201, + 194, 152, 139, 149, 77, 117, 221, 100, 63, 54, 185, 146, + ], + // level 18 + [ + 15, 87, 197, 87, 30, 154, 78, 171, 73, 226, 200, 207, 5, 13, 174, 148, 138, 239, 110, 173, + 100, 115, 146, 39, 53, 70, 36, 157, 28, 31, 241, 15, + ], + // level 19 + [ + 24, 48, 238, 103, 181, 251, 85, 74, 213, 246, 61, 67, 136, 128, 14, 28, 254, 120, 227, 16, + 105, 125, 70, 228, 60, 156, 227, 97, 52, 247, 44, 202, + ], + // level 20 + [ + 33, 52, 231, 106, 197, 210, 26, 171, 24, 108, 43, 225, 221, 143, 132, 238, 136, 10, 30, 70, + 234, 247, 18, 249, 211, 113, 182, 223, 34, 25, 31, 62, + ], +]; + +/// Derives the ladder from the hash function. +/// +/// This is the reference implementation and the source `ZERO_LADDER` was +/// generated from. Nothing on the hot path calls it — the on-chain program and +/// both accumulator views read the baked constant instead — but it is what +/// `the_baked_ladder_matches_the_computed_one` checks that constant against, and +/// it is how the constant is regenerated if the hash ever changes. pub fn zero_ladder() -> Result<[Field; TREE_DEPTH + 1], MirrorError> { let mut z = [Field::ZERO; TREE_DEPTH + 1]; for i in 1..=TREE_DEPTH { @@ -33,6 +150,17 @@ pub fn zero_ladder() -> Result<[Field; TREE_DEPTH + 1], MirrorError> { Ok(z) } +/// The baked ladder as `Field`s, validating canonicality on the way through. +/// +/// Costs no hashing, which is why the on-chain deposit path can afford it. +pub fn baked_ladder() -> Result<[Field; TREE_DEPTH + 1], MirrorError> { + let mut z = [Field::ZERO; TREE_DEPTH + 1]; + for (slot, bytes) in z.iter_mut().zip(ZERO_LADDER.iter()) { + *slot = Field::from_bytes(*bytes)?; + } + Ok(z) +} + /// Insert-only accumulator holding one node per level. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Frontier { @@ -44,7 +172,7 @@ pub struct Frontier { impl Frontier { pub fn new() -> Result { - let zeros = zero_ladder()?; + let zeros = baked_ladder()?; Ok(Frontier { filled: [Field::ZERO; TREE_DEPTH], zeros, @@ -131,7 +259,7 @@ impl MerkleTree { pub fn new() -> Result { Ok(MerkleTree { leaves: Vec::new(), - zeros: zero_ladder()?, + zeros: baked_ladder()?, }) } @@ -204,6 +332,35 @@ mod tests { assert_eq!(frontier.root(), zero_ladder().unwrap()[TREE_DEPTH]); } + /// The constant is only safe because of this test. If the hash function, + /// its parameters, or the endianness ever change, the baked ladder becomes + /// silently wrong — every root would be computed against empty subtrees that + /// no longer exist, and deposits would still succeed while no proof could + /// ever verify. This catches that at build time instead. + #[test] + fn the_baked_ladder_matches_the_computed_one() { + let computed = zero_ladder().unwrap(); + let baked = baked_ladder().unwrap(); + for level in 0..=TREE_DEPTH { + assert_eq!( + computed[level], baked[level], + "ZERO_LADDER is stale at level {level}; regenerate it" + ); + } + } + + #[test] + fn the_baked_ladder_entries_are_canonical_field_elements() { + // A non-canonical constant would be rejected by the Poseidon syscall at + // runtime, on-chain, with no useful diagnostic. + for (level, bytes) in ZERO_LADDER.iter().enumerate() { + assert!( + Field::from_bytes(*bytes).is_ok(), + "ZERO_LADDER[{level}] is not a canonical field element" + ); + } + } + #[test] fn the_zero_ladder_climbs() { let z = zero_ladder().unwrap(); From 3e86da28a8fa98c3ae8e39779a7626ceb58083d0 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 04:22:34 -0300 Subject: [PATCH 08/83] feat(program): pool account layout and the accounting invariant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Explicit fixed offsets rather than a serialisation framework: the layout is the on-chain ABI and is worth reading in one place, every accessor is bounds-checked, and the crate forbids unsafe so nothing transmutes account data. Compile-time asserts pin the offsets, since a field inserted in the middle would silently reinterpret every deployed account. required_vault_lamports is the protocol's accounting invariant. Because the denomination is a pool constant rather than a hidden field, the amount owed is a function of two counters and nothing a prover supplies can influence it. The counter refuses a spend that would exceed deposits outright, so the drain shape that breaks both competing implementations is rejected before any lamport moves rather than caught downstream. Root history is 128 entries. A competitor keeps 32 and appends two leaves per spend, so proofs there age out after roughly sixteen operations. Also fixes two build defects found while verifying: - cargo build-sbf fed a host .dylib for a proc-macro to the SBF rustc when it shared a target directory with the host build. The SBF build now gets its own, and the program stays a workspace member so cargo test --workspace still covers it — excluding it is how a competing submission ended up never running any of its on-chain tests in CI. - That directory must be an absolute path. cargo resolves a relative CARGO_TARGET_DIR against the manifest directory, so the build landed under programs/mirror-pool/ while post-processing looked at the workspace root, and the build reported success while producing no .so at all. --- .github/workflows/ci.yml | 4 +- Makefile | 43 +++ programs/mirror-pool/src/error.rs | 61 ++++ programs/mirror-pool/src/lib.rs | 13 +- programs/mirror-pool/src/state.rs | 453 ++++++++++++++++++++++++++++++ 5 files changed, 570 insertions(+), 4 deletions(-) create mode 100644 Makefile create mode 100644 programs/mirror-pool/src/error.rs create mode 100644 programs/mirror-pool/src/state.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b4dfadd1..88c5049b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,8 @@ jobs: echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH" - run: solana --version - name: Build the on-chain program - run: cargo build-sbf --manifest-path programs/mirror-pool/Cargo.toml + # Isolated target dir: see the note in the Makefile. Sharing one with the + # host build makes cargo feed a host .dylib to the SBF rustc. + run: make build-sbf - name: Run the on-chain test suite against the built .so run: cargo test -p mirror-pool-program --all-features diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..9d9cd099 --- /dev/null +++ b/Makefile @@ -0,0 +1,43 @@ +.PHONY: all test lint fmt build-sbf clean verify + +# cargo build-sbf compiles for sbf-solana-solana, but proc-macro dependencies +# still have to be built for the host. If the shared target directory already +# holds host artifacts from `cargo test` or `cargo clippy`, cargo can hand the +# SBF rustc a host .dylib for one of those macros, which it rejects with +# "extern location ... is of an unknown type". Giving the SBF build its own +# target directory removes the collision. +# +# The program deliberately stays a member of the root workspace so that +# `cargo test --workspace` covers it. Excluding it would hide its tests from CI, +# which is how a competing submission ended up never running a single one of its +# on-chain tests. +# +# The path must be absolute. cargo resolves a relative CARGO_TARGET_DIR against +# the manifest directory, so `target/sbf` with `--manifest-path programs/...` +# builds into programs/mirror-pool/target/sbf while cargo-build-sbf's +# post-processing looks for the artifact under the workspace root, and the build +# "succeeds" while producing no .so. +SBF_TARGET_DIR := $(CURDIR)/target/sbf + +all: verify + +fmt: + cargo fmt --all + +lint: + cargo fmt --all -- --check + cargo clippy --workspace --all-targets -- -D warnings + +test: + cargo test --workspace --all-features + +build-sbf: + CARGO_TARGET_DIR=$(SBF_TARGET_DIR) cargo build-sbf --manifest-path programs/mirror-pool/Cargo.toml + @ls -l $(SBF_TARGET_DIR)/deploy/*.so + +# Everything CI runs, in the order CI runs it. +verify: lint test build-sbf + +clean: + cargo clean + rm -rf $(SBF_TARGET_DIR) diff --git a/programs/mirror-pool/src/error.rs b/programs/mirror-pool/src/error.rs new file mode 100644 index 00000000..84b471d7 --- /dev/null +++ b/programs/mirror-pool/src/error.rs @@ -0,0 +1,61 @@ +//! Program errors. +//! +//! Every variant is a distinct code so that a failing transaction says which +//! invariant it violated. Devnet evidence records these codes alongside the +//! signature, which is what makes a negative test checkable by a third party +//! rather than a claim in a README. + +use solana_program::program_error::ProgramError; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u32)] +pub enum MirrorProgramError { + /// The instruction data was empty, truncated, or carried an unknown tag. + MalformedInstruction = 1, + /// An account was not the PDA the program derives for that role. + InvalidPda = 2, + /// A required signature was absent. + MissingSignature = 3, + /// An account was owned by the wrong program. + InvalidOwner = 4, + /// The pool account has the wrong length or an unrecognised version. + InvalidPoolAccount = 5, + /// The pool is already initialised. + AlreadyInitialised = 6, + /// A pool parameter was outside its permitted range. + InvalidParameter = 7, + /// A 32-byte value was not a canonical BN254 scalar. + NonCanonicalField = 8, + /// Poseidon hashing failed. + PoseidonFailed = 9, + /// The accumulator is full. + TreeFull = 10, + /// The deposit did not carry exactly the pool's denomination plus fee. + WrongDepositAmount = 11, + /// Arithmetic overflowed or underflowed. + ArithmeticOverflow = 12, + /// The vault does not hold enough to cover every unspent note. + /// + /// This is the accounting invariant. It is checked rather than assumed + /// because the two competing implementations are both drainable at exactly + /// this point. + InsolventVault = 13, +} + +impl From for ProgramError { + fn from(e: MirrorProgramError) -> Self { + ProgramError::Custom(e as u32) + } +} + +impl From for MirrorProgramError { + fn from(e: mirror_core::MirrorError) -> Self { + use mirror_core::MirrorError as E; + match e { + E::NonCanonicalField | E::BadFieldLength => MirrorProgramError::NonCanonicalField, + E::Poseidon => MirrorProgramError::PoseidonFailed, + E::TreeFull => MirrorProgramError::TreeFull, + E::BadPathLength | E::LeafIndexOutOfRange => MirrorProgramError::MalformedInstruction, + } + } +} diff --git a/programs/mirror-pool/src/lib.rs b/programs/mirror-pool/src/lib.rs index 97806959..9f97a8d8 100644 --- a/programs/mirror-pool/src/lib.rs +++ b/programs/mirror-pool/src/lib.rs @@ -1,4 +1,11 @@ -//! mirror-pool: a behavioral anonymity set. A member's protocol action is executed -//! by the pool PDA, gated by a Groth16 membership proof verified on-chain, so an -//! observer sees that an action happened but cannot attribute it to a member. +//! mirror-pool: a behavioral anonymity set. A member's protocol action is +//! executed by the pool PDA, gated by a Groth16 membership proof verified +//! on-chain, so an observer sees that an action happened but cannot attribute it +//! to a member. #![forbid(unsafe_code)] + +pub mod error; +pub mod state; + +pub use error::MirrorProgramError; +pub use state::{Pool, POOL_LEN, POOL_VERSION, ROOT_HISTORY}; diff --git a/programs/mirror-pool/src/state.rs b/programs/mirror-pool/src/state.rs new file mode 100644 index 00000000..8f7dc570 --- /dev/null +++ b/programs/mirror-pool/src/state.rs @@ -0,0 +1,453 @@ +//! The pool account. +//! +//! Laid out explicitly at fixed offsets rather than through a serialisation +//! framework. The layout is the on-chain ABI, so it is worth being able to read +//! it in one place, and every accessor is bounds-checked — the crate forbids +//! unsafe, so there are no transmutes over account data. +//! +//! Integers are little-endian, matching Solana convention. Field elements are +//! canonical big-endian 32-byte scalars, matching the Poseidon syscall and the +//! Groth16 verifier, so no value is ever byte-swapped in flight. + +use crate::error::MirrorProgramError; +use mirror_core::{Field, TREE_DEPTH, ZERO_LADDER}; + +/// How many past roots stay acceptable. +/// +/// A prover builds a proof against whatever root was current when they read the +/// chain; by the time a relay lands the transaction, later deposits may have +/// advanced it. The ring is how long a proof stays valid. +/// +/// 128 is deliberate. A competing implementation uses 32 *and* appends two +/// leaves per spend, so a proof there ages out after about sixteen operations +/// and fails under any real load. +pub const ROOT_HISTORY: usize = 128; + +/// Byte offsets. Kept together so the layout can be audited as a unit. +mod offset { + use super::*; + pub const VERSION: usize = 0; + pub const BUMP: usize = 1; + pub const VAULT_BUMP: usize = 2; + pub const DEPTH: usize = 3; + pub const DENOMINATION: usize = 4; + pub const ENTRY_FEE: usize = 12; + pub const K_FLOOR: usize = 20; + pub const DEPOSIT_COUNT: usize = 24; + pub const SPEND_COUNT: usize = 32; + pub const NEXT_INDEX: usize = 40; + pub const ROOT_POS: usize = 48; + pub const _RESERVED: usize = 52; + pub const FILLED: usize = 56; + pub const ROOTS: usize = FILLED + 32 * TREE_DEPTH; + pub const END: usize = ROOTS + 32 * ROOT_HISTORY; +} + +/// Total size of the pool account. +pub const POOL_LEN: usize = offset::END; + +/// The current layout version. Bumping it invalidates old accounts rather than +/// reinterpreting their bytes. +pub const POOL_VERSION: u8 = 1; + +// Pin the layout. A field inserted in the middle would otherwise silently +// reinterpret every deployed account. +const _: () = assert!(offset::FILLED == 56); +const _: () = assert!(offset::ROOTS == 696); +const _: () = assert!(POOL_LEN == 4792); + +/// A borrowed, bounds-checked view over the pool account's bytes. +pub struct Pool<'a> { + data: &'a mut [u8], +} + +macro_rules! read_u64 { + ($self:ident, $off:expr) => {{ + let mut b = [0u8; 8]; + b.copy_from_slice(&$self.data[$off..$off + 8]); + u64::from_le_bytes(b) + }}; +} + +macro_rules! write_u64 { + ($self:ident, $off:expr, $v:expr) => { + $self.data[$off..$off + 8].copy_from_slice(&$v.to_le_bytes()) + }; +} + +impl<'a> Pool<'a> { + /// Wraps account data, rejecting a wrong length or an unknown version. + pub fn load(data: &'a mut [u8]) -> Result { + if data.len() != POOL_LEN { + return Err(MirrorProgramError::InvalidPoolAccount); + } + if data[offset::VERSION] != POOL_VERSION { + return Err(MirrorProgramError::InvalidPoolAccount); + } + if data[offset::DEPTH] as usize != TREE_DEPTH { + return Err(MirrorProgramError::InvalidPoolAccount); + } + Ok(Pool { data }) + } + + /// Wraps a freshly allocated, all-zero account for initialisation. + pub fn load_uninitialised(data: &'a mut [u8]) -> Result { + if data.len() != POOL_LEN { + return Err(MirrorProgramError::InvalidPoolAccount); + } + if data[offset::VERSION] != 0 { + return Err(MirrorProgramError::AlreadyInitialised); + } + Ok(Pool { data }) + } + + pub fn bump(&self) -> u8 { + self.data[offset::BUMP] + } + pub fn vault_bump(&self) -> u8 { + self.data[offset::VAULT_BUMP] + } + pub fn denomination(&self) -> u64 { + read_u64!(self, offset::DENOMINATION) + } + pub fn entry_fee(&self) -> u64 { + read_u64!(self, offset::ENTRY_FEE) + } + pub fn k_floor(&self) -> u32 { + let mut b = [0u8; 4]; + b.copy_from_slice(&self.data[offset::K_FLOOR..offset::K_FLOOR + 4]); + u32::from_le_bytes(b) + } + pub fn deposit_count(&self) -> u64 { + read_u64!(self, offset::DEPOSIT_COUNT) + } + pub fn spend_count(&self) -> u64 { + read_u64!(self, offset::SPEND_COUNT) + } + pub fn next_index(&self) -> u64 { + read_u64!(self, offset::NEXT_INDEX) + } + fn root_pos(&self) -> u32 { + let mut b = [0u8; 4]; + b.copy_from_slice(&self.data[offset::ROOT_POS..offset::ROOT_POS + 4]); + u32::from_le_bytes(b) + } + + /// Writes the immutable parameters and seeds the accumulator to an empty + /// tree of the configured depth. + #[allow(clippy::too_many_arguments)] + pub fn initialise( + &mut self, + bump: u8, + vault_bump: u8, + denomination: u64, + entry_fee: u64, + k_floor: u32, + ) -> Result<(), MirrorProgramError> { + if denomination == 0 { + return Err(MirrorProgramError::InvalidParameter); + } + // A zero floor would let a lone member act with an anonymity set of one, + // which is worse than not using the pool at all: it advertises that a + // privacy tool was used while providing no cover. + if k_floor == 0 { + return Err(MirrorProgramError::InvalidParameter); + } + + self.data[offset::VERSION] = POOL_VERSION; + self.data[offset::BUMP] = bump; + self.data[offset::VAULT_BUMP] = vault_bump; + self.data[offset::DEPTH] = TREE_DEPTH as u8; + write_u64!(self, offset::DENOMINATION, denomination); + write_u64!(self, offset::ENTRY_FEE, entry_fee); + self.data[offset::K_FLOOR..offset::K_FLOOR + 4].copy_from_slice(&k_floor.to_le_bytes()); + + // An empty tree's frontier is the zero ladder, and its root is the top + // of that ladder. Seeding from the shared constant is what keeps the + // program and the host prover on one accumulator. + for (level, zero) in ZERO_LADDER.iter().take(TREE_DEPTH).enumerate() { + let at = offset::FILLED + level * 32; + self.data[at..at + 32].copy_from_slice(zero); + } + self.push_root(Field::from_bytes(ZERO_LADDER[TREE_DEPTH])?); + Ok(()) + } + + pub fn filled(&self, level: usize) -> Result { + if level >= TREE_DEPTH { + return Err(MirrorProgramError::MalformedInstruction); + } + let at = offset::FILLED + level * 32; + let mut b = [0u8; 32]; + b.copy_from_slice(&self.data[at..at + 32]); + Ok(Field::from_bytes(b)?) + } + + pub fn set_filled(&mut self, level: usize, value: Field) -> Result<(), MirrorProgramError> { + if level >= TREE_DEPTH { + return Err(MirrorProgramError::MalformedInstruction); + } + let at = offset::FILLED + level * 32; + self.data[at..at + 32].copy_from_slice(value.as_bytes()); + Ok(()) + } + + /// Appends a root to the ring and makes it current. + pub fn push_root(&mut self, root: Field) { + let pos = (self.root_pos() as usize + 1) % ROOT_HISTORY; + let at = offset::ROOTS + pos * 32; + self.data[at..at + 32].copy_from_slice(root.as_bytes()); + self.data[offset::ROOT_POS..offset::ROOT_POS + 4] + .copy_from_slice(&(pos as u32).to_le_bytes()); + } + + pub fn current_root(&self) -> Result { + let at = offset::ROOTS + self.root_pos() as usize * 32; + let mut b = [0u8; 32]; + b.copy_from_slice(&self.data[at..at + 32]); + Ok(Field::from_bytes(b)?) + } + + /// Whether `root` appears anywhere in the retained history. + /// + /// The all-zero slots of a young ring are skipped, so a proof against a + /// literal zero root can never be accepted by accident. + pub fn knows_root(&self, root: Field) -> bool { + if root.is_zero() { + return false; + } + (0..ROOT_HISTORY).any(|i| { + let at = offset::ROOTS + i * 32; + self.data[at..at + 32] == root.to_bytes() + }) + } + + pub fn record_deposit(&mut self, new_index: u64) -> Result<(), MirrorProgramError> { + let count = self + .deposit_count() + .checked_add(1) + .ok_or(MirrorProgramError::ArithmeticOverflow)?; + write_u64!(self, offset::DEPOSIT_COUNT, count); + write_u64!(self, offset::NEXT_INDEX, new_index); + Ok(()) + } + + pub fn record_spend(&mut self) -> Result<(), MirrorProgramError> { + let count = self + .spend_count() + .checked_add(1) + .ok_or(MirrorProgramError::ArithmeticOverflow)?; + // A spend without a matching deposit would mean the nullifier set and + // the accumulator disagree, so refuse rather than record it. + if count > self.deposit_count() { + return Err(MirrorProgramError::InsolventVault); + } + write_u64!(self, offset::SPEND_COUNT, count); + Ok(()) + } + + /// Notes that have been deposited and not yet spent. + pub fn outstanding_notes(&self) -> Result { + self.deposit_count() + .checked_sub(self.spend_count()) + .ok_or(MirrorProgramError::ArithmeticOverflow) + } + + /// Lamports the vault must still hold to cover every unspent note. + /// + /// The accounting invariant of the whole protocol. Because the denomination + /// is a pool constant rather than a hidden field, the amount owed is a + /// function of two counters and cannot be influenced by anything a prover + /// supplies. The two competing implementations are drainable precisely + /// because they lack this: one escrows an amount never bound to its + /// commitment, the other pays out once per epoch forever against one deposit. + pub fn required_vault_lamports(&self) -> Result { + self.outstanding_notes()? + .checked_mul(self.denomination()) + .ok_or(MirrorProgramError::ArithmeticOverflow) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn blank() -> Vec { + vec![0u8; POOL_LEN] + } + + fn initialised(denomination: u64) -> Vec { + let mut data = blank(); + { + let mut pool = Pool::load_uninitialised(&mut data).unwrap(); + pool.initialise(254, 253, denomination, 1_000, 4).unwrap(); + } + data + } + + #[test] + fn a_fresh_pool_reads_back_its_parameters() { + let mut data = initialised(100_000); + let pool = Pool::load(&mut data).unwrap(); + assert_eq!(pool.bump(), 254); + assert_eq!(pool.vault_bump(), 253); + assert_eq!(pool.denomination(), 100_000); + assert_eq!(pool.entry_fee(), 1_000); + assert_eq!(pool.k_floor(), 4); + assert_eq!(pool.deposit_count(), 0); + assert_eq!(pool.spend_count(), 0); + assert_eq!(pool.next_index(), 0); + } + + #[test] + fn a_fresh_pool_starts_at_the_empty_tree_root() { + let mut data = initialised(1); + let pool = Pool::load(&mut data).unwrap(); + let empty = mirror_core::Frontier::new().unwrap(); + assert_eq!( + pool.current_root().unwrap(), + empty.root(), + "the program and the host must start from one accumulator" + ); + } + + #[test] + fn the_frontier_is_seeded_from_the_zero_ladder() { + let mut data = initialised(1); + let pool = Pool::load(&mut data).unwrap(); + let ladder = mirror_core::baked_ladder().unwrap(); + for (level, expected) in ladder.iter().take(TREE_DEPTH).enumerate() { + assert_eq!(pool.filled(level).unwrap(), *expected); + } + } + + #[test] + fn a_wrong_length_account_is_refused() { + let mut short = vec![0u8; POOL_LEN - 1]; + assert!(matches!( + Pool::load(&mut short), + Err(MirrorProgramError::InvalidPoolAccount) + )); + let mut long = vec![0u8; POOL_LEN + 1]; + assert!(matches!( + Pool::load(&mut long), + Err(MirrorProgramError::InvalidPoolAccount) + )); + } + + #[test] + fn an_uninitialised_account_is_not_loadable_as_a_pool() { + let mut data = blank(); + assert!(matches!( + Pool::load(&mut data), + Err(MirrorProgramError::InvalidPoolAccount) + )); + } + + #[test] + fn initialising_twice_is_refused() { + let mut data = initialised(1); + assert!(matches!( + Pool::load_uninitialised(&mut data), + Err(MirrorProgramError::AlreadyInitialised) + )); + } + + #[test] + fn a_zero_denomination_or_zero_floor_is_refused() { + let mut data = blank(); + let mut pool = Pool::load_uninitialised(&mut data).unwrap(); + assert!(matches!( + pool.initialise(1, 1, 0, 0, 4), + Err(MirrorProgramError::InvalidParameter) + )); + assert!(matches!( + pool.initialise(1, 1, 100, 0, 0), + Err(MirrorProgramError::InvalidParameter) + )); + } + + #[test] + fn the_root_ring_remembers_and_then_forgets() { + let mut data = initialised(1); + let mut pool = Pool::load(&mut data).unwrap(); + + let first = Field::from_u64(1_000); + pool.push_root(first); + assert!(pool.knows_root(first)); + assert_eq!(pool.current_root().unwrap(), first); + + // Fill the ring exactly once more; the first root should still be the + // oldest survivor. + for i in 1..ROOT_HISTORY { + pool.push_root(Field::from_u64(1_000 + i as u64)); + } + assert!(pool.knows_root(first), "evicted one push too early"); + + pool.push_root(Field::from_u64(9_999)); + assert!(!pool.knows_root(first), "should have aged out by now"); + } + + #[test] + fn an_unknown_root_is_not_accepted() { + let mut data = initialised(1); + let pool = Pool::load(&mut data).unwrap(); + assert!(!pool.knows_root(Field::from_u64(12345))); + } + + #[test] + fn a_zero_root_is_never_accepted() { + // A young ring is mostly zeroes. Treating zero as a known root would let + // a proof against an all-zero root pass while the ring is still filling. + let mut data = initialised(1); + let pool = Pool::load(&mut data).unwrap(); + assert!(!pool.knows_root(Field::ZERO)); + } + + #[test] + fn the_vault_requirement_tracks_outstanding_notes() { + let mut data = initialised(1_000_000); + let mut pool = Pool::load(&mut data).unwrap(); + assert_eq!(pool.required_vault_lamports().unwrap(), 0); + + for i in 1..=5u64 { + pool.record_deposit(i).unwrap(); + } + assert_eq!(pool.outstanding_notes().unwrap(), 5); + assert_eq!(pool.required_vault_lamports().unwrap(), 5_000_000); + + pool.record_spend().unwrap(); + pool.record_spend().unwrap(); + assert_eq!(pool.outstanding_notes().unwrap(), 3); + assert_eq!(pool.required_vault_lamports().unwrap(), 3_000_000); + } + + /// The shape of the drain that breaks both competing implementations: more + /// payouts than deposits. Here it is not a matter of catching it late in the + /// spend path — the counter itself refuses. + #[test] + fn spending_more_notes_than_were_deposited_is_refused() { + let mut data = initialised(1_000); + let mut pool = Pool::load(&mut data).unwrap(); + pool.record_deposit(1).unwrap(); + pool.record_spend().unwrap(); + assert!(matches!( + pool.record_spend(), + Err(MirrorProgramError::InsolventVault) + )); + assert_eq!( + pool.spend_count(), + 1, + "the refused spend must not be recorded" + ); + } + + #[test] + fn a_level_outside_the_tree_is_refused_rather_than_wrapping() { + let mut data = initialised(1); + let mut pool = Pool::load(&mut data).unwrap(); + assert!(pool.filled(TREE_DEPTH).is_err()); + assert!(pool.set_filled(TREE_DEPTH, Field::ZERO).is_err()); + assert!(pool.filled(TREE_DEPTH - 1).is_ok()); + } +} From ad3be2ba58ea6686c59d0c2ed832e9b002b36683 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 04:24:25 -0300 Subject: [PATCH 09/83] feat(program): instruction encoding, fail-closed on shape Every variant parses at an exact length; a short buffer is not zero-extended and a trailing byte is not tolerated. every_wrong_length_is_refused walks every prefix of every encoding plus an overlong one, so 'fails closed on shape' is a checked property rather than a README claim. --- programs/mirror-pool/src/instruction.rs | 221 ++++++++++++++++++++++++ programs/mirror-pool/src/lib.rs | 2 + 2 files changed, 223 insertions(+) create mode 100644 programs/mirror-pool/src/instruction.rs diff --git a/programs/mirror-pool/src/instruction.rs b/programs/mirror-pool/src/instruction.rs new file mode 100644 index 00000000..1ac1f0e1 --- /dev/null +++ b/programs/mirror-pool/src/instruction.rs @@ -0,0 +1,221 @@ +//! Instruction encoding. +//! +//! Every variant parses at an exact length. A trailing byte, a missing byte, or +//! an unknown tag is rejected rather than tolerated — there is no "read what you +//! need and ignore the rest", because that is how a caller ends up believing it +//! sent a field the program never read. +//! +//! Encoding is hand-rolled rather than borsh: the instruction data is part of +//! the on-chain ABI, the shapes are small and fixed, and a hand-rolled decoder +//! is one place to audit for length handling. + +use crate::error::MirrorProgramError; + +/// Instruction tags. Stable across versions; new instructions take new tags +/// rather than reusing a retired one. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum Tag { + InitPool = 0, + Deposit = 1, +} + +impl Tag { + fn from_u8(v: u8) -> Result { + match v { + 0 => Ok(Tag::InitPool), + 1 => Ok(Tag::Deposit), + _ => Err(MirrorProgramError::MalformedInstruction), + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Instruction { + /// Creates the pool and its vault, and seeds the accumulator to an empty + /// tree. + InitPool { + denomination: u64, + entry_fee: u64, + k_floor: u32, + }, + /// Escrows exactly `denomination + entry_fee` and appends `commitment` to + /// the accumulator. + /// + /// The amount is not a parameter. It is read from the pool, so a deposit + /// cannot claim a size the pool did not set. + Deposit { commitment: [u8; 32] }, +} + +/// `InitPool`: tag + u64 + u64 + u32. +pub const INIT_POOL_LEN: usize = 1 + 8 + 8 + 4; +/// `Deposit`: tag + one field element. +pub const DEPOSIT_LEN: usize = 1 + 32; + +fn read_u64(data: &[u8], at: usize) -> u64 { + let mut b = [0u8; 8]; + b.copy_from_slice(&data[at..at + 8]); + u64::from_le_bytes(b) +} + +fn read_u32(data: &[u8], at: usize) -> u32 { + let mut b = [0u8; 4]; + b.copy_from_slice(&data[at..at + 4]); + u32::from_le_bytes(b) +} + +impl Instruction { + pub fn unpack(data: &[u8]) -> Result { + let tag = Tag::from_u8( + *data + .first() + .ok_or(MirrorProgramError::MalformedInstruction)?, + )?; + + match tag { + Tag::InitPool => { + if data.len() != INIT_POOL_LEN { + return Err(MirrorProgramError::MalformedInstruction); + } + Ok(Instruction::InitPool { + denomination: read_u64(data, 1), + entry_fee: read_u64(data, 9), + k_floor: read_u32(data, 17), + }) + } + Tag::Deposit => { + if data.len() != DEPOSIT_LEN { + return Err(MirrorProgramError::MalformedInstruction); + } + let mut commitment = [0u8; 32]; + commitment.copy_from_slice(&data[1..33]); + Ok(Instruction::Deposit { commitment }) + } + } + } + + /// Serialises for a client. Kept beside the decoder so the two cannot drift. + pub fn pack(&self) -> Vec { + match self { + Instruction::InitPool { + denomination, + entry_fee, + k_floor, + } => { + let mut out = Vec::with_capacity(INIT_POOL_LEN); + out.push(Tag::InitPool as u8); + out.extend_from_slice(&denomination.to_le_bytes()); + out.extend_from_slice(&entry_fee.to_le_bytes()); + out.extend_from_slice(&k_floor.to_le_bytes()); + out + } + Instruction::Deposit { commitment } => { + let mut out = Vec::with_capacity(DEPOSIT_LEN); + out.push(Tag::Deposit as u8); + out.extend_from_slice(commitment); + out + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn init() -> Instruction { + Instruction::InitPool { + denomination: 100_000_000, + entry_fee: 5_000, + k_floor: 8, + } + } + + fn deposit() -> Instruction { + Instruction::Deposit { + commitment: [3u8; 32], + } + } + + #[test] + fn packing_then_unpacking_is_the_identity() { + for ix in [init(), deposit()] { + assert_eq!(Instruction::unpack(&ix.pack()).unwrap(), ix); + } + } + + #[test] + fn the_encoded_lengths_are_pinned() { + // These are the on-chain ABI. A change here breaks every deployed client. + assert_eq!(init().pack().len(), INIT_POOL_LEN); + assert_eq!(deposit().pack().len(), DEPOSIT_LEN); + assert_eq!(INIT_POOL_LEN, 21); + assert_eq!(DEPOSIT_LEN, 33); + } + + #[test] + fn empty_data_is_refused() { + assert!(matches!( + Instruction::unpack(&[]), + Err(MirrorProgramError::MalformedInstruction) + )); + } + + #[test] + fn an_unknown_tag_is_refused() { + for tag in [2u8, 3, 99, 255] { + assert!( + matches!( + Instruction::unpack(&[tag]), + Err(MirrorProgramError::MalformedInstruction) + ), + "tag {tag} was accepted" + ); + } + } + + /// Fail-closed on shape, checked rather than asserted in a README. A short + /// buffer must not be zero-extended and a long one must not be truncated. + #[test] + fn every_wrong_length_is_refused() { + for ix in [init(), deposit()] { + let good = ix.pack(); + for len in 0..good.len() { + assert!( + Instruction::unpack(&good[..len]).is_err(), + "a {len}-byte prefix was accepted for {ix:?}" + ); + } + let mut long = good.clone(); + long.push(0); + assert!( + Instruction::unpack(&long).is_err(), + "a trailing byte was tolerated for {ix:?}" + ); + } + } + + #[test] + fn fields_decode_at_the_right_offsets() { + // Distinct values so a swapped offset cannot pass by coincidence. + let ix = Instruction::InitPool { + denomination: 0x1122_3344_5566_7788, + entry_fee: 0x99aa_bbcc_ddee_ff00, + k_floor: 0xdead_beef, + }; + assert_eq!(Instruction::unpack(&ix.pack()).unwrap(), ix); + } + + #[test] + fn a_deposit_commitment_survives_verbatim() { + let mut commitment = [0u8; 32]; + for (i, b) in commitment.iter_mut().enumerate() { + *b = i as u8; + } + let ix = Instruction::Deposit { commitment }; + match Instruction::unpack(&ix.pack()).unwrap() { + Instruction::Deposit { commitment: got } => assert_eq!(got, commitment), + other => panic!("decoded as {other:?}"), + } + } +} diff --git a/programs/mirror-pool/src/lib.rs b/programs/mirror-pool/src/lib.rs index 9f97a8d8..ba6796b2 100644 --- a/programs/mirror-pool/src/lib.rs +++ b/programs/mirror-pool/src/lib.rs @@ -5,7 +5,9 @@ #![forbid(unsafe_code)] pub mod error; +pub mod instruction; pub mod state; pub use error::MirrorProgramError; +pub use instruction::Instruction; pub use state::{Pool, POOL_LEN, POOL_VERSION, ROOT_HISTORY}; From e506844bbe4ab1ad1fab88149962f2843898b1a5 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 04:28:03 -0300 Subject: [PATCH 10/83] feat(program): init_pool and deposit, with the accumulator parity asserted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One pool per denomination, globally, and creation is permissionless. That is a privacy decision rather than a convenience one: splitting deposits of the same size across pools splits the anonymity set, and a split set is worse for every member in it. There is also no privileged authority, so no key whose loss freezes the escrow. The deposit amount is read from the pool, never from the instruction, so a deposit cannot claim a size the pool did not set — that is the half of the accounting invariant a competing implementation lacks, where escrowed lamports are a caller-supplied parameter never bound to the hidden commitment. The invariant is then re-read from the vault after the transfers land rather than assumed to hold. Escrow lives in its own vault PDA with no data, so the invariant reads against a balance holding nothing but escrow and rent; entry fees accrue on the pool account instead, where they can never be mistaken for lamports backing a note. the_program_insert_matches_the_host_accumulator walks 17 deposits and asserts the program's on-chain frontier and the host's produce identical indices and identical roots at every step. A divergence there means every proof the host builds targets a root the chain will never hold. Duplicate commitments are deliberately not rejected, with the reasoning recorded: it would cost a marker account per deposit, and a duplicate only burns the money of whoever submitted it. --- Cargo.lock | 4 + Cargo.toml | 2 + programs/mirror-pool/Cargo.toml | 13 + programs/mirror-pool/src/lib.rs | 14 + programs/mirror-pool/src/pda.rs | 66 + programs/mirror-pool/src/processor.rs | 271 +++ sp4/solana-program-4.0.0/.cargo_vcs_info.json | 6 + sp4/solana-program-4.0.0/Cargo.lock | 1912 +++++++++++++++++ sp4/solana-program-4.0.0/Cargo.toml | 287 +++ sp4/solana-program-4.0.0/Cargo.toml.orig | 113 + sp4/solana-program-4.0.0/README.md | 15 + sp4/solana-program-4.0.0/src/bpf_loader.rs | 23 + .../src/bpf_loader_deprecated.rs | 13 + sp4/solana-program-4.0.0/src/compute_units.rs | 13 + .../src/ed25519_program.rs | 4 + .../src/entrypoint_deprecated.rs | 139 ++ .../src/epoch_schedule.rs | 8 + sp4/solana-program-4.0.0/src/hash.rs | 11 + sp4/solana-program-4.0.0/src/incinerator.rs | 2 + sp4/solana-program-4.0.0/src/instruction.rs | 52 + sp4/solana-program-4.0.0/src/lamports.rs | 6 + sp4/solana-program-4.0.0/src/lib.rs | 690 ++++++ sp4/solana-program-4.0.0/src/log.rs | 101 + sp4/solana-program-4.0.0/src/program.rs | 215 ++ sp4/solana-program-4.0.0/src/program_error.rs | 14 + .../src/secp256k1_program.rs | 9 + sp4/solana-program-4.0.0/src/slot_hashes.rs | 1 + sp4/solana-program-4.0.0/src/slot_history.rs | 2 + .../src/syscalls/definitions.rs | 39 + sp4/solana-program-4.0.0/src/syscalls/mod.rs | 25 + sp4/solana-program-4.0.0/src/sysvar.rs | 33 + sp4/sp.crate | Bin 0 -> 30262 bytes 32 files changed, 4103 insertions(+) create mode 100644 programs/mirror-pool/src/pda.rs create mode 100644 programs/mirror-pool/src/processor.rs create mode 100644 sp4/solana-program-4.0.0/.cargo_vcs_info.json create mode 100644 sp4/solana-program-4.0.0/Cargo.lock create mode 100644 sp4/solana-program-4.0.0/Cargo.toml create mode 100644 sp4/solana-program-4.0.0/Cargo.toml.orig create mode 100644 sp4/solana-program-4.0.0/README.md create mode 100644 sp4/solana-program-4.0.0/src/bpf_loader.rs create mode 100644 sp4/solana-program-4.0.0/src/bpf_loader_deprecated.rs create mode 100644 sp4/solana-program-4.0.0/src/compute_units.rs create mode 100644 sp4/solana-program-4.0.0/src/ed25519_program.rs create mode 100644 sp4/solana-program-4.0.0/src/entrypoint_deprecated.rs create mode 100644 sp4/solana-program-4.0.0/src/epoch_schedule.rs create mode 100644 sp4/solana-program-4.0.0/src/hash.rs create mode 100644 sp4/solana-program-4.0.0/src/incinerator.rs create mode 100644 sp4/solana-program-4.0.0/src/instruction.rs create mode 100644 sp4/solana-program-4.0.0/src/lamports.rs create mode 100644 sp4/solana-program-4.0.0/src/lib.rs create mode 100644 sp4/solana-program-4.0.0/src/log.rs create mode 100644 sp4/solana-program-4.0.0/src/program.rs create mode 100644 sp4/solana-program-4.0.0/src/program_error.rs create mode 100644 sp4/solana-program-4.0.0/src/secp256k1_program.rs create mode 100644 sp4/solana-program-4.0.0/src/slot_hashes.rs create mode 100644 sp4/solana-program-4.0.0/src/slot_history.rs create mode 100644 sp4/solana-program-4.0.0/src/syscalls/definitions.rs create mode 100644 sp4/solana-program-4.0.0/src/syscalls/mod.rs create mode 100644 sp4/solana-program-4.0.0/src/sysvar.rs create mode 100644 sp4/sp.crate diff --git a/Cargo.lock b/Cargo.lock index d726d0b1..c26f074a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1099,6 +1099,7 @@ dependencies = [ "groth16-solana", "mirror-core", "solana-program", + "solana-system-interface", ] [[package]] @@ -2002,7 +2003,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55b54965bf0b76fa8e2b35376583efddd4d916618cfe595bf48c7d7b55a9e628" dependencies = [ "num-traits", + "serde", + "serde_derive", "solana-address", + "solana-instruction", "solana-msg", "solana-program-error", ] diff --git a/Cargo.toml b/Cargo.toml index 92c0e191..0d9026a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,6 +40,8 @@ light-poseidon = "0.4" ark-crypto-primitives = { version = "0.5", default-features = false, features = ["sponge", "crh", "merkle_tree", "r1cs"] } groth16-solana = "0.2" solana-program = "4.0" +# System instructions moved out of solana-program in 4.0. +solana-system-interface = { version = "3.0", features = ["bincode"] } thiserror = "2" anyhow = "1" serde = { version = "1", features = ["derive"] } diff --git a/programs/mirror-pool/Cargo.toml b/programs/mirror-pool/Cargo.toml index 2e44f290..f04f71b0 100644 --- a/programs/mirror-pool/Cargo.toml +++ b/programs/mirror-pool/Cargo.toml @@ -10,4 +10,17 @@ crate-type = ["cdylib", "lib"] [dependencies] mirror-core.workspace = true solana-program.workspace = true +solana-system-interface.workspace = true groth16-solana.workspace = true + +[features] +no-entrypoint = [] + +# The entrypoint! macro emits these; declaring them keeps `-D warnings` honest +# rather than silencing the lint wholesale. +[lints.rust.unexpected_cfgs] +level = "warn" +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("custom-heap", "custom-panic"))', +] diff --git a/programs/mirror-pool/src/lib.rs b/programs/mirror-pool/src/lib.rs index ba6796b2..7547df30 100644 --- a/programs/mirror-pool/src/lib.rs +++ b/programs/mirror-pool/src/lib.rs @@ -6,8 +6,22 @@ pub mod error; pub mod instruction; +pub mod pda; +pub mod processor; pub mod state; pub use error::MirrorProgramError; pub use instruction::Instruction; pub use state::{Pool, POOL_LEN, POOL_VERSION, ROOT_HISTORY}; + +#[cfg(not(feature = "no-entrypoint"))] +solana_program::entrypoint!(entry); + +#[cfg(not(feature = "no-entrypoint"))] +fn entry( + program_id: &solana_program::pubkey::Pubkey, + accounts: &[solana_program::account_info::AccountInfo], + data: &[u8], +) -> solana_program::entrypoint::ProgramResult { + processor::process(program_id, accounts, data) +} diff --git a/programs/mirror-pool/src/pda.rs b/programs/mirror-pool/src/pda.rs new file mode 100644 index 00000000..443eef45 --- /dev/null +++ b/programs/mirror-pool/src/pda.rs @@ -0,0 +1,66 @@ +//! Program-derived addresses. +//! +//! One pool per denomination, globally. That is a privacy decision rather than a +//! convenience one: fragmenting deposits of the same size across several pools +//! splits the anonymity set, and a split set is strictly worse for every member +//! in it. Making the denomination the only seed means two users choosing "0.1 +//! SOL" cannot accidentally end up in different crowds. + +use solana_program::pubkey::Pubkey; + +pub const POOL_SEED: &[u8] = b"pool"; +pub const VAULT_SEED: &[u8] = b"vault"; + +/// The pool account for a denomination. +pub fn pool_address(program_id: &Pubkey, denomination: u64) -> (Pubkey, u8) { + Pubkey::find_program_address(&[POOL_SEED, &denomination.to_le_bytes()], program_id) +} + +/// The vault that escrows a pool's notes. +/// +/// Kept separate from the pool account so that the accounting invariant reads +/// against a balance holding nothing but escrow and its own rent. Entry fees +/// accrue on the pool account instead, so reward lamports can never be mistaken +/// for lamports backing an unspent note. +pub fn vault_address(program_id: &Pubkey, pool: &Pubkey) -> (Pubkey, u8) { + Pubkey::find_program_address(&[VAULT_SEED, pool.as_ref()], program_id) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn addresses_are_deterministic() { + let program = Pubkey::new_unique(); + let (a, bump_a) = pool_address(&program, 100_000); + let (b, bump_b) = pool_address(&program, 100_000); + assert_eq!(a, b); + assert_eq!(bump_a, bump_b); + } + + #[test] + fn each_denomination_gets_its_own_pool() { + let program = Pubkey::new_unique(); + assert_ne!( + pool_address(&program, 100_000).0, + pool_address(&program, 200_000).0 + ); + } + + #[test] + fn the_vault_is_derived_from_the_pool_not_the_denomination() { + let program = Pubkey::new_unique(); + let (pool, _) = pool_address(&program, 100_000); + let (vault, _) = vault_address(&program, &pool); + assert_ne!(vault, pool); + assert_eq!(vault, vault_address(&program, &pool).0); + } + + #[test] + fn a_different_program_yields_different_addresses() { + let (a, _) = pool_address(&Pubkey::new_unique(), 1); + let (b, _) = pool_address(&Pubkey::new_unique(), 1); + assert_ne!(a, b); + } +} diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs new file mode 100644 index 00000000..4869b281 --- /dev/null +++ b/programs/mirror-pool/src/processor.rs @@ -0,0 +1,271 @@ +//! Instruction handlers. + +use crate::{ + error::MirrorProgramError, + instruction::Instruction, + pda::{pool_address, vault_address, POOL_SEED, VAULT_SEED}, + state::{Pool, POOL_LEN}, +}; +use mirror_core::{hash_node, Field, TREE_DEPTH}; +use solana_program::{ + account_info::{next_account_info, AccountInfo}, + entrypoint::ProgramResult, + program::invoke_signed, + pubkey::Pubkey, + rent::Rent, + sysvar::Sysvar, +}; +use solana_system_interface::{instruction as system_instruction, program as system_program}; + +pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult { + match Instruction::unpack(data)? { + Instruction::InitPool { + denomination, + entry_fee, + k_floor, + } => init_pool(program_id, accounts, denomination, entry_fee, k_floor), + Instruction::Deposit { commitment } => deposit(program_id, accounts, commitment), + } +} + +/// Creates the pool and its vault and seeds the accumulator to an empty tree. +/// +/// Permissionless: the first caller for a denomination creates the crowd +/// everyone else joins. There is no privileged authority, so there is no key +/// whose loss freezes the pool — a competing implementation bakes its authority +/// into the pool's PDA seeds with no rotation instruction, which makes its +/// advertised rotating relay undeployable and its escrow permanently hostage. +fn init_pool( + program_id: &Pubkey, + accounts: &[AccountInfo], + denomination: u64, + entry_fee: u64, + k_floor: u32, +) -> ProgramResult { + let iter = &mut accounts.iter(); + let payer = next_account_info(iter)?; + let pool_account = next_account_info(iter)?; + let vault_account = next_account_info(iter)?; + let system = next_account_info(iter)?; + + if !payer.is_signer { + return Err(MirrorProgramError::MissingSignature.into()); + } + if !system_program::check_id(system.key) { + return Err(MirrorProgramError::InvalidOwner.into()); + } + + let (expected_pool, pool_bump) = pool_address(program_id, denomination); + if *pool_account.key != expected_pool { + return Err(MirrorProgramError::InvalidPda.into()); + } + let (expected_vault, vault_bump) = vault_address(program_id, &expected_pool); + if *vault_account.key != expected_vault { + return Err(MirrorProgramError::InvalidPda.into()); + } + + let rent = Rent::get()?; + let denom_le = denomination.to_le_bytes(); + + // The pool account carries the accumulator and, above its own rent, the + // reward pool that entry fees accrue into. + invoke_signed( + &system_instruction::create_account( + payer.key, + pool_account.key, + rent.minimum_balance(POOL_LEN), + POOL_LEN as u64, + program_id, + ), + &[payer.clone(), pool_account.clone(), system.clone()], + &[&[POOL_SEED, &denom_le, &[pool_bump]]], + )?; + + // The vault holds escrow only, and carries no data of its own: the + // accounting invariant is a statement about its lamports, so anything else + // living there would muddy it. + invoke_signed( + &system_instruction::create_account( + payer.key, + vault_account.key, + rent.minimum_balance(0), + 0, + program_id, + ), + &[payer.clone(), vault_account.clone(), system.clone()], + &[&[VAULT_SEED, expected_pool.as_ref(), &[vault_bump]]], + )?; + + let mut data = pool_account.try_borrow_mut_data()?; + let mut pool = Pool::load_uninitialised(&mut data)?; + pool.initialise(pool_bump, vault_bump, denomination, entry_fee, k_floor)?; + Ok(()) +} + +/// Escrows exactly one denomination and appends a note commitment. +/// +/// The escrowed amount is read from the pool, never from the instruction, so a +/// deposit cannot claim a size the pool did not set. This is the half of the +/// accounting invariant that a competing implementation is missing: there, the +/// escrowed lamports are a caller-supplied parameter that is never bound to the +/// hidden commitment, so a depositor of one lamport can later withdraw the whole +/// pool with an entirely valid proof. +/// +/// Duplicate commitments are not rejected. Doing so would cost a marker account +/// per deposit, and the only party a duplicate harms is whoever submitted it: +/// they cannot know the original's secret, so they can never spend the note they +/// paid for. It burns their own money and leaves every other member unaffected. +fn deposit(program_id: &Pubkey, accounts: &[AccountInfo], commitment: [u8; 32]) -> ProgramResult { + let iter = &mut accounts.iter(); + let depositor = next_account_info(iter)?; + let pool_account = next_account_info(iter)?; + let vault_account = next_account_info(iter)?; + let system = next_account_info(iter)?; + + if !depositor.is_signer { + return Err(MirrorProgramError::MissingSignature.into()); + } + if !system_program::check_id(system.key) { + return Err(MirrorProgramError::InvalidOwner.into()); + } + if pool_account.owner != program_id || vault_account.owner != program_id { + return Err(MirrorProgramError::InvalidOwner.into()); + } + + // A canonical scalar, checked here rather than at first use. A commitment + // at or above the modulus would be rejected by the Poseidon syscall deep in + // the insert, after lamports had already moved. + let leaf = Field::from_bytes(commitment).map_err(MirrorProgramError::from)?; + + let (denomination, entry_fee) = { + let mut data = pool_account.try_borrow_mut_data()?; + let pool = Pool::load(&mut data)?; + if *vault_account.key != vault_address(program_id, pool_account.key).0 { + return Err(MirrorProgramError::InvalidPda.into()); + } + (pool.denomination(), pool.entry_fee()) + }; + + solana_program::program::invoke( + &system_instruction::transfer(depositor.key, vault_account.key, denomination), + &[depositor.clone(), vault_account.clone(), system.clone()], + )?; + if entry_fee > 0 { + solana_program::program::invoke( + &system_instruction::transfer(depositor.key, pool_account.key, entry_fee), + &[depositor.clone(), pool_account.clone(), system.clone()], + )?; + } + + let mut data = pool_account.try_borrow_mut_data()?; + let mut pool = Pool::load(&mut data)?; + let index = insert_leaf(&mut pool, leaf)?; + pool.record_deposit(index + 1)?; + + // Re-read the invariant from the account after the transfers landed, rather + // than trusting that they did. + let rent = Rent::get()?; + let required = pool + .required_vault_lamports()? + .checked_add(rent.minimum_balance(0)) + .ok_or(MirrorProgramError::ArithmeticOverflow)?; + if vault_account.lamports() < required { + return Err(MirrorProgramError::InsolventVault.into()); + } + Ok(()) +} + +/// Appends `leaf` to the pool's frontier, returning its index. +/// +/// The same walk as `mirror_core::Frontier::insert`, over account bytes. The +/// two are asserted to agree in the program's test suite, because a divergence +/// here means the host builds proofs against a root the chain will never hold. +fn insert_leaf(pool: &mut Pool<'_>, leaf: Field) -> Result { + let index = pool.next_index(); + if index >= 1u64 << TREE_DEPTH { + return Err(MirrorProgramError::TreeFull); + } + + let ladder = mirror_core::baked_ladder().map_err(MirrorProgramError::from)?; + let mut current = leaf; + let mut path = index; + + for (level, zero) in ladder.iter().take(TREE_DEPTH).enumerate() { + if path & 1 == 0 { + pool.set_filled(level, current)?; + current = hash_node(current, *zero).map_err(MirrorProgramError::from)?; + } else { + let left = pool.filled(level)?; + current = hash_node(left, current).map_err(MirrorProgramError::from)?; + } + path >>= 1; + } + + pool.push_root(current); + Ok(index) +} + +#[cfg(test)] +mod tests { + use super::*; + use mirror_core::Frontier; + + /// The program's accumulator and the host's must produce identical roots + /// over the same insertion sequence. If they ever diverge, every proof the + /// host builds targets a root the chain does not have. + #[test] + fn the_program_insert_matches_the_host_accumulator() { + let mut data = vec![0u8; POOL_LEN]; + { + let mut pool = Pool::load_uninitialised(&mut data).unwrap(); + pool.initialise(255, 254, 1_000_000, 0, 2).unwrap(); + } + let mut host = Frontier::new().unwrap(); + let mut pool_data = data; + + for i in 1..=17u64 { + let leaf = Field::from_u64(i * 1_000 + 7); + let host_index = host.insert(leaf).unwrap(); + + let mut pool = Pool::load(&mut pool_data).unwrap(); + let program_index = insert_leaf(&mut pool, leaf).unwrap(); + pool.record_deposit(program_index + 1).unwrap(); + + assert_eq!(program_index, host_index, "leaf index diverged at {i}"); + assert_eq!( + pool.current_root().unwrap(), + host.root(), + "root diverged after {i} insertions" + ); + } + } + + #[test] + fn every_intermediate_root_stays_known() { + let mut data = vec![0u8; POOL_LEN]; + { + let mut pool = Pool::load_uninitialised(&mut data).unwrap(); + pool.initialise(255, 254, 1, 0, 2).unwrap(); + } + let mut roots = Vec::new(); + for i in 1..=20u64 { + let mut pool = Pool::load(&mut data).unwrap(); + insert_leaf(&mut pool, Field::from_u64(i)).unwrap(); + roots.push(pool.current_root().unwrap()); + } + let pool = Pool::load(&mut data).unwrap(); + for (i, root) in roots.iter().enumerate() { + assert!( + pool.knows_root(*root), + "root after deposit {i} was forgotten while still inside the ring" + ); + } + } + + #[test] + fn a_non_canonical_commitment_is_refused_before_anything_moves() { + // The value the Poseidon syscall would reject much later, after + // lamports had already been transferred. + assert!(Field::from_bytes([0xff; 32]).is_err()); + } +} diff --git a/sp4/solana-program-4.0.0/.cargo_vcs_info.json b/sp4/solana-program-4.0.0/.cargo_vcs_info.json new file mode 100644 index 00000000..89cd2758 --- /dev/null +++ b/sp4/solana-program-4.0.0/.cargo_vcs_info.json @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "12b63164cc1854b798362190583de5d1f2fe8091" + }, + "path_in_vcs": "program" +} \ No newline at end of file diff --git a/sp4/solana-program-4.0.0/Cargo.lock b/sp4/solana-program-4.0.0/Cargo.lock new file mode 100644 index 00000000..bfc1a68a --- /dev/null +++ b/sp4/solana-program-4.0.0/Cargo.lock @@ -0,0 +1,1912 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[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 = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "2.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" + +[[package]] +name = "bitmaps" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" +dependencies = [ + "typenum", +] + +[[package]] +name = "blake3" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" +dependencies = [ + "arrayref", + "arrayvec", + "cc", + "cfg-if", + "constant_time_eq", +] + +[[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 = "borsh" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "boxcar" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66bb12751a83493ef4b8da1120451a262554e216a247f14b48cb5e8fe7ed8bdf" + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" +dependencies = [ + "feature-probe", + "serde", +] + +[[package]] +name = "bytemuck" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" + +[[package]] +name = "bytemuck_derive" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" +dependencies = [ + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "constant_time_eq" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[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 0.6.4", + "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 = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rand_core 0.6.4", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "serde", +] + +[[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 = "derive_arbitrary" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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 = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "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", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "feature-probe" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "five8" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23f76610e969fa1784327ded240f1e28a3fd9520c9cec93b636fcf62dd37f772" +dependencies = [ + "five8_core", +] + +[[package]] +name = "five8_const" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a0f1728185f277989ca573a402716ae0beaaea3f76a8ff87ef9dd8fb19436c5" +dependencies = [ + "five8_core", +] + +[[package]] +name = "five8_core" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "059c31d7d36c43fe39d89e55711858b4da8be7eb6dabac23c7289b1a19489406" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[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 = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + +[[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" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "im" +version = "15.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" +dependencies = [ + "bitmaps", + "rand_core 0.6.4", + "rand_xoshiro", + "rayon", + "serde", + "sized-chunks", + "typenum", + "version_check", +] + +[[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", +] + +[[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", + "signature", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.170" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" + +[[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 = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memmap2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[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-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[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 = "pastey" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[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-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.3", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xoshiro" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "redox_syscall" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +dependencies = [ + "bitflags", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[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 = "semver" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" + +[[package]] +name = "serde" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.226" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_with" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +dependencies = [ + "serde", + "serde_derive", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sha2-const-stable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest", + "keccak", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "sized-chunks" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" +dependencies = [ + "bitmaps", + "typenum", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "solana-account-info" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc3397241392f5756925029acaa8515dc70fcbe3d8059d4885d7d6533baf64fd" +dependencies = [ + "bincode", + "serde_core", + "solana-address 2.2.0", + "solana-program-error", + "solana-program-memory", +] + +[[package]] +name = "solana-address" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ecac8e1b7f74c2baa9e774c42817e3e75b20787134b76cc4d45e8a604488f5" +dependencies = [ + "solana-address 2.2.0", +] + +[[package]] +name = "solana-address" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68c5d02824391b072dc5cd0aaa85fb0af9784a21d23286a767994d1e8a322131" +dependencies = [ + "arbitrary", + "borsh", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "five8", + "five8_const", + "rand", + "serde", + "serde_derive", + "sha2-const-stable", + "solana-atomic-u64", + "solana-define-syscall 5.0.0", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-program-error", + "solana-sanitize", + "solana-sha256-hasher", + "wincode", +] + +[[package]] +name = "solana-atomic-u64" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a933ff1e50aff72d02173cfcd7511bd8540b027ee720b75f353f594f834216d0" +dependencies = [ + "parking_lot", +] + +[[package]] +name = "solana-big-mod-exp" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30c80fb6d791b3925d5ec4bf23a7c169ef5090c013059ec3ed7d0b2c04efa085" +dependencies = [ + "num-bigint", + "num-traits", + "solana-define-syscall 3.0.0", +] + +[[package]] +name = "solana-blake3-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7116e1d942a2432ca3f514625104757ab8a56233787e95144c93950029e31176" +dependencies = [ + "blake3", + "solana-define-syscall 4.0.1", + "solana-hash 4.2.0", +] + +[[package]] +name = "solana-borsh" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc402b16657abbfa9991cd5cbfac5a11d809f7e7d28d3bb291baeb088b39060e" +dependencies = [ + "borsh", +] + +[[package]] +name = "solana-clock" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb62e9381182459a4520b5fe7fb22d423cae736239a6427fc398a88743d0ed59" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-cpi" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dea26709d867aada85d0d3617db0944215c8bb28d3745b912de7db13a23280c" +dependencies = [ + "solana-account-info", + "solana-define-syscall 4.0.1", + "solana-instruction", + "solana-program-error", + "solana-pubkey 4.1.0", + "solana-stable-layout", +] + +[[package]] +name = "solana-define-syscall" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9697086a4e102d28a156b8d6b521730335d6951bd39a5e766512bbe09007cee" + +[[package]] +name = "solana-define-syscall" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" + +[[package]] +name = "solana-define-syscall" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03aacdd7a61e2109887a7a7f046caebafce97ddf1150f33722eeac04f9039c73" + +[[package]] +name = "solana-epoch-rewards" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b319a4ed70390af911090c020571f0ff1f4ec432522d05ab89f5c08080381995" +dependencies = [ + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-hash 3.1.0", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-epoch-schedule" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5481e72cc4d52c169db73e4c0cd16de8bc943078aac587ec4817a75cc6388f" +dependencies = [ + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-epoch-stake" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc6693d0ea833b880514b9b88d95afb80b42762dca98b0712465d1fcbbcb89e" +dependencies = [ + "solana-define-syscall 3.0.0", + "solana-pubkey 3.0.0", +] + +[[package]] +name = "solana-example-mocks" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb265ff95e28eceda117e2e3d2d2a611ecbbfe911dfeeeecd1521814540ffab" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 4.2.0", + "solana-instruction", + "solana-nonce", + "solana-pubkey 4.1.0", + "solana-sdk-ids", + "solana-system-interface", + "thiserror", +] + +[[package]] +name = "solana-fee-calculator" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b2a5675b2cf8d407c672dc1776492b1f382337720ddf566645ae43237a3d8c3" +dependencies = [ + "log", + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", +] + +[[package]] +name = "solana-frozen-abi" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10284a0ca713bcce197cff2c5408efe06ebe6b6bcc868074d4ccb71b5c60028" +dependencies = [ + "bincode", + "boxcar", + "bs58", + "bv", + "bytes", + "dashmap", + "im", + "log", + "memmap2", + "rand", + "rand_chacha", + "serde", + "serde_derive", + "serde_with", + "sha2", + "solana-frozen-abi-macro", + "thiserror", +] + +[[package]] +name = "solana-frozen-abi-macro" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5396c179ca7d76f866b102eb3819ca3922bdaa33c9ada8005f4e98fd59ab65f5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "solana-hash" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "337c246447142f660f778cf6cb582beba8e28deb05b3b24bfb9ffd7c562e5f41" +dependencies = [ + "solana-hash 4.2.0", +] + +[[package]] +name = "solana-hash" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8064ea1d591ec791be95245058ca40f4f5345d390c200069d0f79bbf55bfae55" +dependencies = [ + "borsh", + "bytemuck", + "bytemuck_derive", + "five8", + "serde", + "serde_derive", + "solana-atomic-u64", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sanitize", + "wincode", +] + +[[package]] +name = "solana-instruction" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1b699a2c1518028a9982e255e0eca10c44d90006542d9d7f9f40dbce3f7c78" +dependencies = [ + "bincode", + "borsh", + "serde", + "serde_derive", + "solana-define-syscall 4.0.1", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-instruction-error", + "solana-pubkey 4.1.0", +] + +[[package]] +name = "solana-instruction-error" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b04259e03c05faf38a8c24217b5cfe4c90572ae6184ab49cddb1584fdd756d3f" +dependencies = [ + "num-traits", + "solana-program-error", +] + +[[package]] +name = "solana-instructions-sysvar" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ddf67876c541aa1e21ee1acae35c95c6fbc61119814bfef70579317a5e26955" +dependencies = [ + "bitflags", + "qualifier_attr", + "solana-account-info", + "solana-instruction", + "solana-instruction-error", + "solana-program-error", + "solana-pubkey 3.0.0", + "solana-sanitize", + "solana-sdk-ids", + "solana-serialize-utils", + "solana-sysvar-id", +] + +[[package]] +name = "solana-keccak-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed1c0d16d6fdeba12291a1f068cdf0d479d9bff1141bf44afd7aa9d485f65ef8" +dependencies = [ + "sha3", + "solana-define-syscall 4.0.1", + "solana-hash 4.2.0", +] + +[[package]] +name = "solana-last-restart-slot" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcda154ec827f5fc1e4da0af3417951b7e9b8157540f81f936c4a8b1156134d0" +dependencies = [ + "serde", + "serde_derive", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-msg" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "264275c556ea7e22b9d3f87d56305546a38d4eee8ec884f3b126236cb7dcbbb4" +dependencies = [ + "solana-define-syscall 3.0.0", +] + +[[package]] +name = "solana-native-token" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8dd4c280dca9d046139eb5b7a5ac9ad10403fbd64964c7d7571214950d758f" + +[[package]] +name = "solana-nonce" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc469152a63284ef959b80c59cda015262a021da55d3b8fe42171d89c4b64f8" +dependencies = [ + "solana-fee-calculator", + "solana-hash 4.2.0", + "solana-pubkey 4.1.0", + "solana-sha256-hasher", +] + +[[package]] +name = "solana-program" +version = "4.0.0" +dependencies = [ + "memoffset", + "solana-account-info", + "solana-big-mod-exp", + "solana-blake3-hasher", + "solana-borsh", + "solana-clock", + "solana-cpi", + "solana-define-syscall 5.0.0", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-epoch-stake", + "solana-example-mocks", + "solana-fee-calculator", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-hash 4.2.0", + "solana-instruction", + "solana-instruction-error", + "solana-instructions-sysvar", + "solana-keccak-hasher", + "solana-last-restart-slot", + "solana-msg", + "solana-native-token", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-program-option", + "solana-program-pack", + "solana-pubkey 4.1.0", + "solana-rent", + "solana-sdk-ids", + "solana-secp256k1-recover", + "solana-serde-varint", + "solana-serialize-utils", + "solana-sha256-hasher", + "solana-short-vec", + "solana-slot-hashes", + "solana-slot-history", + "solana-stable-layout", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", +] + +[[package]] +name = "solana-program-entrypoint" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c9b0a1ff494e05f503a08b3d51150b73aa639544631e510279d6375f290997" +dependencies = [ + "solana-account-info", + "solana-define-syscall 4.0.1", + "solana-program-error", + "solana-pubkey 4.1.0", +] + +[[package]] +name = "solana-program-error" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1af32c995a7b692a915bb7414d5f8e838450cf7c70414e763d8abcae7b51f28" +dependencies = [ + "borsh", + "serde", + "serde_derive", +] + +[[package]] +name = "solana-program-memory" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4068648649653c2c50546e9a7fb761791b5ab0cda054c771bb5808d3a4b9eb52" +dependencies = [ + "solana-define-syscall 4.0.1", +] + +[[package]] +name = "solana-program-option" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7b4ddb464f274deb4a497712664c3b612e3f5f82471d4e47710fc4ab1c3095" + +[[package]] +name = "solana-program-pack" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c169359de21f6034a63ebf96d6b380980307df17a8d371344ff04a883ec4e9d0" +dependencies = [ + "solana-program-error", +] + +[[package]] +name = "solana-pubkey" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8909d399deb0851aa524420beeb5646b115fd253ef446e35fe4504c904da3941" +dependencies = [ + "solana-address 1.1.0", +] + +[[package]] +name = "solana-pubkey" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b06bd918d60111ee1f97de817113e2040ca0cedb740099ee8d646233f6b906c" +dependencies = [ + "solana-address 2.2.0", +] + +[[package]] +name = "solana-rent" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763fe5c88a76ce18235db595b21d38b7aebf6db56b324cdf9fc96059f4410823" +dependencies = [ + "serde", + "serde_derive", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-sysvar-id", +] + +[[package]] +name = "solana-sanitize" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" + +[[package]] +name = "solana-sdk-ids" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def234c1956ff616d46c9dd953f251fa7096ddbaa6d52b165218de97882b7280" +dependencies = [ + "solana-address 2.2.0", +] + +[[package]] +name = "solana-sdk-macro" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6430000e97083460b71d9fbadc52a2ab2f88f53b3a4c5e58c5ae3640a0e8c00" +dependencies = [ + "bs58", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "solana-secp256k1-recover" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de18cfdab99eeb940fbedd8c981fa130c0d76252da75d05446f22fae8b51932" +dependencies = [ + "k256", + "solana-define-syscall 4.0.1", + "thiserror", +] + +[[package]] +name = "solana-serde-varint" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e5174c57d5ff3c1995f274d17156964664566e2cde18a07bba1586d35a70d3b" +dependencies = [ + "serde", +] + +[[package]] +name = "solana-serialize-utils" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e41dd8feea239516c623a02f0a81c2367f4b604d7965237fed0751aeec33ed" +dependencies = [ + "solana-instruction-error", + "solana-pubkey 3.0.0", + "solana-sanitize", +] + +[[package]] +name = "solana-sha256-hasher" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db7dc3011ea4c0334aaaa7e7128cb390ecf546b28d412e9bf2064680f57f588f" +dependencies = [ + "sha2", + "solana-define-syscall 4.0.1", + "solana-hash 4.2.0", +] + +[[package]] +name = "solana-short-vec" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3bd991c2cc415291c86bb0b6b4d53e93d13bb40344e4c5a2884e0e4f5fa93f" +dependencies = [ + "solana-frozen-abi", + "solana-frozen-abi-macro", +] + +[[package]] +name = "solana-slot-hashes" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80a293f952293281443c04f4d96afd9d547721923d596e92b4377ed2360f1746" +dependencies = [ + "serde", + "serde_derive", + "solana-hash 3.1.0", + "solana-sdk-ids", + "solana-sysvar-id", +] + +[[package]] +name = "solana-slot-history" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f914f6b108f5bba14a280b458d023e3621c9973f27f015a4d755b50e88d89e97" +dependencies = [ + "bv", + "serde", + "serde_derive", + "solana-sdk-ids", + "solana-sysvar-id", +] + +[[package]] +name = "solana-stable-layout" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1da74507795b6e8fb60b7c7306c0c36e2c315805d16eaaf479452661234685ac" +dependencies = [ + "solana-instruction", + "solana-pubkey 3.0.0", +] + +[[package]] +name = "solana-system-interface" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14591d6508042ebefb110305d3ba761615927146a26917ade45dc332d8e1ecde" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-address 2.2.0", + "solana-instruction", + "solana-msg", + "solana-program-error", +] + +[[package]] +name = "solana-sysvar" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1632b69b4f72489db5949a10e8308c229dfa003f99ecaa7477b376807c7b81f4" +dependencies = [ + "base64", + "bincode", + "bytemuck", + "bytemuck_derive", + "lazy_static", + "serde", + "serde_derive", + "solana-account-info", + "solana-clock", + "solana-define-syscall 5.0.0", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-fee-calculator", + "solana-frozen-abi", + "solana-frozen-abi-macro", + "solana-hash 4.2.0", + "solana-instruction", + "solana-last-restart-slot", + "solana-program-entrypoint", + "solana-program-error", + "solana-program-memory", + "solana-pubkey 4.1.0", + "solana-rent", + "solana-sdk-ids", + "solana-sdk-macro", + "solana-slot-hashes", + "solana-slot-history", + "solana-sysvar-id", +] + +[[package]] +name = "solana-sysvar-id" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17358d1e9a13e5b9c2264d301102126cf11a47fd394cdf3dec174fe7bc96e1de" +dependencies = [ + "solana-address 2.2.0", + "solana-sdk-ids", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tinyvec" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" + +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_write", + "winnow", +] + +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wincode" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "466e67917609b2d40a838a5b972d1a6237c9749600cb8de8f65559b90d48485b" +dependencies = [ + "pastey", + "proc-macro2", + "quote", + "thiserror", + "wincode-derive", +] + +[[package]] +name = "wincode-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26a7a568eda854acc9945ed136a9d50b8c6d31911584624958808ae96eee3912" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[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.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[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", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/sp4/solana-program-4.0.0/Cargo.toml b/sp4/solana-program-4.0.0/Cargo.toml new file mode 100644 index 00000000..fc626fb6 --- /dev/null +++ b/sp4/solana-program-4.0.0/Cargo.toml @@ -0,0 +1,287 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.81.0" +name = "solana-program" +version = "4.0.0" +authors = ["Anza Maintainers "] +build = false +include = [ + "src/**/*", + "README.md", +] +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Solana Program" +homepage = "https://anza.xyz/" +documentation = "https://docs.rs/solana-program" +readme = "README.md" +license = "Apache-2.0" +repository = "https://github.com/anza-xyz/solana-sdk" + +[package.metadata.docs.rs] +targets = [ + "x86_64-unknown-linux-gnu", + "wasm32-unknown-unknown", +] +all-features = true +rustdoc-args = ["--cfg=docsrs"] + +[features] +borsh = [ + "dep:solana-borsh", + "solana-hash/borsh", + "solana-instruction/borsh", + "solana-program-error/borsh", + "solana-pubkey/borsh", +] +default = ["borsh"] +dev-context-only-utils = [ + "solana-instructions-sysvar/dev-context-only-utils", + "dep:solana-system-interface", + "solana-system-interface/bincode", +] +frozen-abi = [ + "dep:solana-frozen-abi", + "dep:solana-frozen-abi-macro", + "solana-epoch-rewards/frozen-abi", + "solana-epoch-schedule/frozen-abi", + "solana-fee-calculator/frozen-abi", + "solana-hash/frozen-abi", + "solana-instruction/frozen-abi", + "solana-pubkey/frozen-abi", + "solana-rent/frozen-abi", + "solana-short-vec/frozen-abi", + "solana-sysvar/frozen-abi", +] + +[lib] +name = "solana_program" +crate-type = ["rlib"] +path = "src/lib.rs" + +[dependencies.memoffset] +version = "0.9" + +[dependencies.solana-account-info] +version = "3.0.0" +features = ["bincode"] + +[dependencies.solana-big-mod-exp] +version = "3.0.0" + +[dependencies.solana-blake3-hasher] +version = "3.0.0" +features = ["blake3"] + +[dependencies.solana-borsh] +version = "3.0.0" +optional = true + +[dependencies.solana-clock] +version = "3.0.0" +features = [ + "serde", + "sysvar", +] + +[dependencies.solana-cpi] +version = "3.0.0" + +[dependencies.solana-epoch-rewards] +version = "3.0.0" +features = [ + "serde", + "sysvar", +] + +[dependencies.solana-epoch-schedule] +version = "3.0.0" +features = [ + "serde", + "sysvar", +] + +[dependencies.solana-epoch-stake] +version = "3.0.0" + +[dependencies.solana-fee-calculator] +version = "3.1.0" +features = ["serde"] + +[dependencies.solana-frozen-abi] +version = "3.0.0" +features = ["frozen-abi"] +optional = true + +[dependencies.solana-frozen-abi-macro] +version = "3.0.1" +features = ["frozen-abi"] +optional = true + +[dependencies.solana-hash] +version = "4.2.0" +features = [ + "bytemuck", + "serde", + "std", +] +default-features = false + +[dependencies.solana-instruction] +version = "3.0.0" +features = [ + "bincode", + "serde", + "std", + "syscalls", +] +default-features = false + +[dependencies.solana-instruction-error] +version = "2.0.0" +features = ["num-traits"] + +[dependencies.solana-instructions-sysvar] +version = "3.0.0" + +[dependencies.solana-keccak-hasher] +version = "3.0.0" +features = ["sha3"] + +[dependencies.solana-last-restart-slot] +version = "3.0.0" +features = [ + "serde", + "sysvar", +] + +[dependencies.solana-msg] +version = "3.0.0" +features = ["std"] +default-features = false + +[dependencies.solana-native-token] +version = "3.0.0" + +[dependencies.solana-program-entrypoint] +version = "3.0.0" + +[dependencies.solana-program-error] +version = "3.0.0" +features = ["serde"] + +[dependencies.solana-program-memory] +version = "3.0.0" + +[dependencies.solana-program-option] +version = "3.0.0" + +[dependencies.solana-program-pack] +version = "3.0.0" + +[dependencies.solana-pubkey] +version = "4.1.0" +features = [ + "bytemuck", + "curve25519", + "serde", + "std", +] +default-features = false + +[dependencies.solana-rent] +version = "4.0.0" +features = [ + "serde", + "sysvar", +] +default-features = false + +[dependencies.solana-sdk-ids] +version = "3.0.0" + +[dependencies.solana-secp256k1-recover] +version = "3.0.0" + +[dependencies.solana-serde-varint] +version = "3.0.0" + +[dependencies.solana-serialize-utils] +version = "3.1.0" + +[dependencies.solana-sha256-hasher] +version = "3.0.0" +features = ["sha2"] + +[dependencies.solana-short-vec] +version = "3.2.0" +default-features = false + +[dependencies.solana-slot-hashes] +version = "3.0.0" +features = [ + "serde", + "sysvar", +] + +[dependencies.solana-slot-history] +version = "3.0.0" +features = [ + "serde", + "sysvar", +] + +[dependencies.solana-stable-layout] +version = "3.0.0" + +[dependencies.solana-system-interface] +version = "3.0" +features = ["bincode"] +optional = true + +[dependencies.solana-sysvar] +version = "4.0.0" +features = [ + "bincode", + "bytemuck", +] + +[dependencies.solana-sysvar-id] +version = "3.0.0" + +[dev-dependencies.solana-pubkey] +version = "4.1.0" +features = ["dev-context-only-utils"] +default-features = false + +[dev-dependencies.solana-sysvar] +version = "4.0.0" +features = ["dev-context-only-utils"] + +[target.'cfg(not(target_os = "solana"))'.dependencies.solana-example-mocks] +version = "4.0.0" + +[target.'cfg(target_os = "solana")'.dependencies.solana-define-syscall] +version = "5.0.0" + +[lints.rust.unexpected_cfgs] +level = "warn" +priority = 0 +check-cfg = [ + 'cfg(target_os, values("solana"))', + 'cfg(feature, values("frozen-abi", "no-entrypoint"))', +] diff --git a/sp4/solana-program-4.0.0/Cargo.toml.orig b/sp4/solana-program-4.0.0/Cargo.toml.orig new file mode 100644 index 00000000..d0283e4a --- /dev/null +++ b/sp4/solana-program-4.0.0/Cargo.toml.orig @@ -0,0 +1,113 @@ +[package] +name = "solana-program" +description = "Solana Program" +documentation = "https://docs.rs/solana-program" +readme = "README.md" +version = "4.0.0" +authors = { workspace = true } +repository = { workspace = true } +homepage = { workspace = true } +license = { workspace = true } +edition = { workspace = true } +rust-version = "1.81.0" # solana platform-tools rust version +include = ["src/**/*", "README.md"] + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] +all-features = true +rustdoc-args = ["--cfg=docsrs"] + +[lib] +crate-type = ["rlib"] + +[features] +default = ["borsh"] +borsh = [ + "dep:solana-borsh", + "solana-hash/borsh", + "solana-instruction/borsh", + "solana-program-error/borsh", + "solana-pubkey/borsh", +] +dev-context-only-utils = [ + "solana-instructions-sysvar/dev-context-only-utils", + "dep:solana-system-interface", + "solana-system-interface/bincode", +] +frozen-abi = [ + "dep:solana-frozen-abi", + "dep:solana-frozen-abi-macro", + "solana-epoch-rewards/frozen-abi", + "solana-epoch-schedule/frozen-abi", + "solana-fee-calculator/frozen-abi", + "solana-hash/frozen-abi", + "solana-instruction/frozen-abi", + "solana-pubkey/frozen-abi", + "solana-rent/frozen-abi", + "solana-short-vec/frozen-abi", + "solana-sysvar/frozen-abi", +] + +[dependencies] +memoffset = { workspace = true } +solana-account-info = { workspace = true, features = ["bincode"] } +solana-big-mod-exp = { workspace = true } +solana-blake3-hasher = { workspace = true, features = ["blake3"] } +solana-borsh = { workspace = true, optional = true } +solana-clock = { workspace = true, features = ["serde", "sysvar"] } +solana-cpi = { workspace = true } +solana-epoch-rewards = { workspace = true, features = ["serde", "sysvar"] } +solana-epoch-schedule = { workspace = true, features = ["serde", "sysvar"] } +solana-epoch-stake = { workspace = true } +solana-fee-calculator = { workspace = true, features = ["serde"] } +solana-frozen-abi = { workspace = true, optional = true, features = ["frozen-abi"] } +solana-frozen-abi-macro = { workspace = true, optional = true, features = ["frozen-abi"] } +solana-hash = { workspace = true, features = [ + "bytemuck", + "serde", + "std", +] } +solana-instruction = { workspace = true, default-features = false, features = [ + "bincode", + "serde", + "std", + "syscalls", +] } +solana-instruction-error = { workspace = true, features = ["num-traits"] } +solana-instructions-sysvar = { workspace = true } +solana-keccak-hasher = { workspace = true, features = ["sha3"] } +solana-last-restart-slot = { workspace = true, features = ["serde", "sysvar"] } +solana-msg = { workspace = true, features = ["std"] } +solana-native-token = { workspace = true } +solana-program-entrypoint = { workspace = true } +solana-program-error = { workspace = true, features = ["serde"] } +solana-program-memory = { workspace = true } +solana-program-option = { workspace = true } +solana-program-pack = { workspace = true } +solana-pubkey = { workspace = true, features = ["bytemuck", "curve25519", "serde", "std"] } +solana-rent = { workspace = true, features = ["serde", "sysvar"] } +solana-sdk-ids = { workspace = true } +solana-secp256k1-recover = { workspace = true } +solana-serde-varint = { workspace = true } +solana-serialize-utils = { workspace = true } +solana-sha256-hasher = { workspace = true, features = ["sha2"] } +solana-short-vec = { workspace = true } +solana-slot-hashes = { workspace = true, features = ["serde", "sysvar"] } +solana-slot-history = { workspace = true, features = ["serde", "sysvar"] } +solana-stable-layout = { workspace = true } +solana-system-interface = { optional = true, workspace = true, features = ["bincode"] } +solana-sysvar = { workspace = true, features = ["bincode", "bytemuck"] } +solana-sysvar-id = { workspace = true } + +[target.'cfg(not(target_os = "solana"))'.dependencies] +solana-example-mocks = { workspace = true } + +[target.'cfg(target_os = "solana")'.dependencies] +solana-define-syscall = { workspace = true } + +[dev-dependencies] +solana-pubkey = { workspace = true, features = ["dev-context-only-utils"] } +solana-sysvar = { workspace = true, features = ["dev-context-only-utils"] } + +[lints] +workspace = true diff --git a/sp4/solana-program-4.0.0/README.md b/sp4/solana-program-4.0.0/README.md new file mode 100644 index 00000000..c93d4a14 --- /dev/null +++ b/sp4/solana-program-4.0.0/README.md @@ -0,0 +1,15 @@ +

+ + Solana + +

+ +# Solana Program + +Use the Solana Program Crate to write on-chain programs in Rust. If writing client-side applications, use the [Solana SDK Crate](https://crates.io/crates/solana-sdk) instead. + +More information about Solana is available in the [Solana documentation](https://solana.com/docs). + +[Solana Program Library](https://github.com/solana-labs/solana-program-library) provides examples of how to use this crate. + +Still have questions? Ask us on [Stack Exchange](https://sola.na/sse) diff --git a/sp4/solana-program-4.0.0/src/bpf_loader.rs b/sp4/solana-program-4.0.0/src/bpf_loader.rs new file mode 100644 index 00000000..8d3177b1 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/bpf_loader.rs @@ -0,0 +1,23 @@ +//! The latest BPF loader native program. +//! +//! The BPF loader is responsible for loading, finalizing, and executing BPF +//! programs. Not all networks may support the latest loader. You can use the +//! command-line tools to check if this version of the loader is supported by +//! requesting the account info for the public key below. +//! +//! The program format may change between loaders, and it is crucial to build +//! your program against the proper entrypoint semantics. All programs being +//! deployed to this BPF loader must build against the latest entrypoint version +//! located in `entrypoint.rs`. +//! +//! Note: Programs built for older loaders must use a matching entrypoint +//! version. An example is [`bpf_loader_deprecated`] which requires +//! [`entrypoint_deprecated`]. +//! +//! The `solana program deploy` CLI command uses the +//! [upgradeable BPF loader][ubpfl]. +//! +//! [`bpf_loader_deprecated`]: crate::bpf_loader_deprecated +//! [`entrypoint_deprecated`]: mod@crate::entrypoint_deprecated +//! [ubpfl]: https://docs.rs/solana-loader-v3-interface/latest/solana_loader_v3_interface/index.html +pub use solana_sdk_ids::bpf_loader::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/bpf_loader_deprecated.rs b/sp4/solana-program-4.0.0/src/bpf_loader_deprecated.rs new file mode 100644 index 00000000..8662a5ec --- /dev/null +++ b/sp4/solana-program-4.0.0/src/bpf_loader_deprecated.rs @@ -0,0 +1,13 @@ +//! The original and now deprecated Solana BPF loader. +//! +//! The BPF loader is responsible for loading, finalizing, and executing BPF +//! programs. +//! +//! This loader is deprecated, and it is strongly encouraged to build for and +//! deploy to the latest BPF loader. For more information see `bpf_loader.rs` +//! +//! The program format may change between loaders, and it is crucial to build +//! your program against the proper entrypoint semantics. All programs being +//! deployed to this BPF loader must build against the deprecated entrypoint +//! version located in `entrypoint_deprecated.rs`. +pub use solana_sdk_ids::bpf_loader_deprecated::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/compute_units.rs b/sp4/solana-program-4.0.0/src/compute_units.rs new file mode 100644 index 00000000..6b7f2712 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/compute_units.rs @@ -0,0 +1,13 @@ +/// Return the remaining compute units the program may consume +#[inline] +pub fn sol_remaining_compute_units() -> u64 { + #[cfg(target_os = "solana")] + unsafe { + crate::syscalls::sol_remaining_compute_units() + } + + #[cfg(not(target_os = "solana"))] + { + crate::program_stubs::sol_remaining_compute_units() + } +} diff --git a/sp4/solana-program-4.0.0/src/ed25519_program.rs b/sp4/solana-program-4.0.0/src/ed25519_program.rs new file mode 100644 index 00000000..a41385ad --- /dev/null +++ b/sp4/solana-program-4.0.0/src/ed25519_program.rs @@ -0,0 +1,4 @@ +//! The [ed25519 native program][np]. +//! +//! [np]: https://docs.solanalabs.com/runtime/programs#ed25519-program +pub use solana_sdk_ids::ed25519_program::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/entrypoint_deprecated.rs b/sp4/solana-program-4.0.0/src/entrypoint_deprecated.rs new file mode 100644 index 00000000..60fea304 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/entrypoint_deprecated.rs @@ -0,0 +1,139 @@ +//! The Rust-based BPF program entrypoint supported by the original BPF loader. +//! +//! The original BPF loader is deprecated and exists for backwards-compatibility +//! reasons. This module should not be used by new programs. +//! +//! For more information see the [`bpf_loader_deprecated`] module. +//! +//! [`bpf_loader_deprecated`]: crate::bpf_loader_deprecated + +#![allow(clippy::arithmetic_side_effects)] + +extern crate alloc; +use { + crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}, + alloc::vec::Vec, + std::{ + mem::size_of, + result::Result as ResultGeneric, + slice::{from_raw_parts, from_raw_parts_mut}, + }, +}; + +pub type ProgramResult = ResultGeneric<(), ProgramError>; + +/// User implemented function to process an instruction +/// +/// program_id: Program ID of the currently executing program +/// accounts: Accounts passed as part of the instruction +/// instruction_data: Instruction data +pub type ProcessInstruction = + fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult; + +/// Programs indicate success with a return value of 0 +pub const SUCCESS: u64 = 0; + +/// Declare the program entrypoint. +/// +/// Deserialize the program input arguments and call +/// the user defined `process_instruction` function. +/// Users must call this macro otherwise an entrypoint for +/// their program will not be created. +#[macro_export] +macro_rules! entrypoint_deprecated { + ($process_instruction:ident) => { + /// # Safety + #[no_mangle] + pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { + let (program_id, accounts, instruction_data) = + unsafe { $crate::entrypoint_deprecated::deserialize(input) }; + match $process_instruction(&program_id, &accounts, &instruction_data) { + Ok(()) => $crate::entrypoint_deprecated::SUCCESS, + Err(error) => error.into(), + } + } + }; +} + +/// Deserialize the input arguments +/// +/// # Safety +#[allow(clippy::type_complexity)] +pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec>, &'a [u8]) { + let mut offset: usize = 0; + + // Number of accounts present + + #[allow(clippy::cast_ptr_alignment)] + let num_accounts = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + // Account Infos + + let mut accounts = Vec::with_capacity(num_accounts); + for _ in 0..num_accounts { + let dup_info = *(input.add(offset) as *const u8); + offset += size_of::(); + if dup_info == u8::MAX { + #[allow(clippy::cast_ptr_alignment)] + let is_signer = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let is_writable = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + let key: &Pubkey = &*(input.add(offset) as *const Pubkey); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let lamports = &mut *(input.add(offset) as *mut u64); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let data_len = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + let data = from_raw_parts_mut(input.add(offset), data_len); + offset += data_len; + + let owner: &Pubkey = &*(input.add(offset) as *const Pubkey); + offset += size_of::(); + + #[allow(clippy::cast_ptr_alignment)] + let executable = *(input.add(offset) as *const u8) != 0; + offset += size_of::(); + + // rent epoch is skipped + offset += size_of::(); + + accounts.push(AccountInfo::new( + key, + is_signer, + is_writable, + lamports, + data, + owner, + executable, + )); + } else { + // Duplicate account, clone the original + accounts.push(accounts[dup_info as usize].clone()); + } + } + + // Instruction data + + #[allow(clippy::cast_ptr_alignment)] + let instruction_data_len = *(input.add(offset) as *const u64) as usize; + offset += size_of::(); + + let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) }; + offset += instruction_data_len; + + // Program Id + + let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey); + + (program_id, accounts, instruction_data) +} diff --git a/sp4/solana-program-4.0.0/src/epoch_schedule.rs b/sp4/solana-program-4.0.0/src/epoch_schedule.rs new file mode 100644 index 00000000..56addac3 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/epoch_schedule.rs @@ -0,0 +1,8 @@ +#[deprecated( + since = "2.1.0", + note = "Use solana-clock and solana-epoch-schedule crates instead." +)] +pub use { + solana_clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH}, + solana_epoch_schedule::*, +}; diff --git a/sp4/solana-program-4.0.0/src/hash.rs b/sp4/solana-program-4.0.0/src/hash.rs new file mode 100644 index 00000000..ffdfa0d1 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/hash.rs @@ -0,0 +1,11 @@ +//! Hashing with the [SHA-256] hash function, and a general [`Hash`] type. +//! +//! [SHA-256]: https://en.wikipedia.org/wiki/SHA-2 +//! [`Hash`]: struct@Hash + +#[cfg(not(target_os = "solana"))] +pub use solana_sha256_hasher::Hasher; +pub use { + solana_hash::{Hash, ParseHashError, HASH_BYTES}, + solana_sha256_hasher::{hash, hashv}, +}; diff --git a/sp4/solana-program-4.0.0/src/incinerator.rs b/sp4/solana-program-4.0.0/src/incinerator.rs new file mode 100644 index 00000000..82b54e6b --- /dev/null +++ b/sp4/solana-program-4.0.0/src/incinerator.rs @@ -0,0 +1,2 @@ +#[deprecated(since = "2.2.0", note = "Use `solana_sdk_ids::incinerator` instead")] +pub use solana_sdk_ids::incinerator::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/instruction.rs b/sp4/solana-program-4.0.0/src/instruction.rs new file mode 100644 index 00000000..38fb5131 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/instruction.rs @@ -0,0 +1,52 @@ +pub use solana_instruction::{ + error::InstructionError, AccountMeta, Instruction, ProcessedSiblingInstruction, + TRANSACTION_LEVEL_STACK_HEIGHT, +}; + +/// Returns a sibling instruction from the processed sibling instruction list. +/// +/// The processed sibling instruction list is a reverse-ordered list of +/// successfully processed sibling instructions. For example, given the call flow: +/// +/// A +/// B -> C -> D +/// B -> E +/// B -> F +/// +/// Then B's processed sibling instruction list is: `[A]` +/// Then F's processed sibling instruction list is: `[E, C]` +pub fn get_processed_sibling_instruction(index: usize) -> Option { + #[cfg(target_os = "solana")] + { + solana_instruction::syscalls::get_processed_sibling_instruction(index) + } + + #[cfg(not(target_os = "solana"))] + { + crate::program_stubs::sol_get_processed_sibling_instruction(index) + } +} + +/// Get the current stack height, transaction-level instructions are height +/// TRANSACTION_LEVEL_STACK_HEIGHT, fist invoked inner instruction is height +/// TRANSACTION_LEVEL_STACK_HEIGHT + 1, etc... +pub fn get_stack_height() -> usize { + #[cfg(target_os = "solana")] + { + solana_instruction::syscalls::get_stack_height() + } + + #[cfg(not(target_os = "solana"))] + { + crate::program_stubs::sol_get_stack_height() as usize + } +} + +// TODO: remove this. +/// Addition that returns [`InstructionError::InsufficientFunds`] on overflow. +/// +/// This is an internal utility function. +#[doc(hidden)] +pub fn checked_add(a: u64, b: u64) -> Result { + a.checked_add(b).ok_or(InstructionError::InsufficientFunds) +} diff --git a/sp4/solana-program-4.0.0/src/lamports.rs b/sp4/solana-program-4.0.0/src/lamports.rs new file mode 100644 index 00000000..87cc2b6d --- /dev/null +++ b/sp4/solana-program-4.0.0/src/lamports.rs @@ -0,0 +1,6 @@ +//! Re-exports the [`LamportsError`] type for backwards compatibility. +#[deprecated( + since = "2.1.0", + note = "Use solana_instruction_error::LamportsError instead" +)] +pub use solana_instruction_error::LamportsError; diff --git a/sp4/solana-program-4.0.0/src/lib.rs b/sp4/solana-program-4.0.0/src/lib.rs new file mode 100644 index 00000000..927a2d2d --- /dev/null +++ b/sp4/solana-program-4.0.0/src/lib.rs @@ -0,0 +1,690 @@ +//! The base library for all Solana on-chain Rust programs. +//! +//! All Solana Rust programs that run on-chain will link to this crate, which +//! acts as a standard library for Solana programs. Solana programs also link to +//! the [Rust standard library][std], though it is [modified][sstd] for the +//! Solana runtime environment. While off-chain programs that interact with the +//! Solana network _can_ link to this crate, they typically instead use the +//! [`solana-sdk`] crate, which reexports all modules from `solana-program`. +//! +//! [std]: https://doc.rust-lang.org/stable/std/ +//! [sstd]: https://solana.com/docs/programs/limitations#rust-libraries +//! [`solana-sdk`]: https://docs.rs/solana-sdk/latest/solana_sdk/ +//! +//! This library defines +//! +//! - macros for declaring the [program entrypoint][pe], +//! - [core data types][cdt], +//! - [logging] macros, +//! - [serialization] methods, +//! - methods for [cross-program instruction execution][cpi], +//! - program IDs and instruction constructors for the system program and other +//! [native programs][np], +//! - [sysvar] accessors. +//! +//! [pe]: #defining-a-solana-program +//! [cdt]: #core-data-types +//! [logging]: crate::log +//! [serialization]: #serialization +//! [np]: #native-programs +//! [cpi]: #cross-program-instruction-execution +//! [sysvar]: crate::sysvar +//! +//! Idiomatic examples of `solana-program` usage can be found in +//! [the Solana Program Library][spl]. +//! +//! [spl]: https://github.com/solana-labs/solana-program-library +//! +//! # Defining a solana program +//! +//! Solana program crates have some unique properties compared to typical Rust +//! programs: +//! +//! - They are often compiled for both on-chain use and off-chain use. This is +//! primarily because off-chain clients may need access to data types +//! defined by the on-chain program. +//! - They do not define a `main` function, but instead define their entrypoint +//! with the [`entrypoint!`] macro. +//! - They are compiled as the ["cdylib"] crate type for dynamic loading +//! by the Solana runtime. +//! - They run in a constrained VM environment, and while they do have access to +//! the [Rust standard library][std], many features of the standard library, +//! particularly related to OS services, will fail at runtime, will silently +//! do nothing, or are not defined. See the [restrictions to the Rust standard +//! library][sstd] in the Solana documentation for more. +//! +//! [std]: https://doc.rust-lang.org/std/index.html +//! ["cdylib"]: https://doc.rust-lang.org/reference/linkage.html +//! +//! Because multiple crates that are linked together cannot all define +//! program entrypoints (see the [`entrypoint!`] documentation) a common +//! convention is to use a [Cargo feature] called `no-entrypoint` to allow +//! the program entrypoint to be disabled. +//! +//! [Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html +//! +//! The skeleton of a Solana program typically looks like: +//! +//! ``` +//! #[cfg(not(feature = "no-entrypoint"))] +//! pub mod entrypoint { +//! use solana_program::{ +//! account_info::AccountInfo, +//! entrypoint, +//! entrypoint::ProgramResult, +//! pubkey::Pubkey, +//! }; +//! +//! entrypoint!(process_instruction); +//! +//! pub fn process_instruction( +//! program_id: &Pubkey, +//! accounts: &[AccountInfo], +//! instruction_data: &[u8], +//! ) -> ProgramResult { +//! // Decode and dispatch instructions here. +//! todo!() +//! } +//! } +//! +//! // Additional code goes here. +//! ``` +//! +//! With a `Cargo.toml` file that contains +//! +//! ```toml +//! [lib] +//! crate-type = ["cdylib"] +//! +//! [features] +//! no-entrypoint = [] +//! ``` +//! +//! Note that a Solana program must specify its crate-type as "cdylib", to +//! be discovered and built by the `cargo-build-sbf` command as a deployable program. +//! Solana programs also often have crate-type "rlib" so they can be linked to other Rust crates. +//! Avoid using "rlib" and "cdylib" crates together, since their combined usage precludes +//! compiler optimizations that may decrease program size and CU usage. +//! +//! Prefer writing a separate package if it is supposed to be used as a library for other Solana +//! programs (i.e. a "rlib" only crate). This would be normally the case for defining account +//! types and helpers that are used by both clients and program. When creating a Rust project +//! intended to be a program ready for deployment, use only the "cdylib" crate type. +//! +//! # On-chain vs. off-chain compilation targets +//! +//! Solana programs run on the [rbpf] VM, which implements a variant of the +//! [eBPF] instruction set. Because this crate can be compiled for both on-chain +//! and off-chain execution, the environments of which are significantly +//! different, it extensively uses [conditional compilation][cc] to tailor its +//! implementation to the environment. The `cfg` predicate used for identifying +//! compilation for on-chain programs is `target_os = "solana"`, as in this +//! example from the `solana-program` codebase that logs a message via a +//! syscall when run on-chain, and via a library call when offchain: +//! +//! [rbpf]: https://github.com/solana-labs/rbpf +//! [eBPF]: https://ebpf.io/ +//! [cc]: https://doc.rust-lang.org/reference/conditional-compilation.html +//! +//! ``` +//! pub fn sol_log(message: &str) { +//! #[cfg(target_os = "solana")] +//! unsafe { +//! sol_log_(message.as_ptr(), message.len() as u64); +//! } +//! +//! #[cfg(not(target_os = "solana"))] +//! program_stubs::sol_log(message); +//! } +//! # mod program_stubs { +//! # pub(crate) fn sol_log(message: &str) { } +//! # } +//! ``` +//! +//! This `cfg` pattern is suitable as well for user code that needs to work both +//! on-chain and off-chain. +//! +//! `solana-program` and `solana-sdk` were previously a single crate. Because of +//! this history, and because of the dual-usage of `solana-program` for two +//! different environments, it contains some features that are not available to +//! on-chain programs at compile-time. It also contains some on-chain features +//! that will fail in off-chain scenarios at runtime. This distinction is not +//! well-reflected in the documentation. +//! +//! For a more complete description of Solana's implementation of eBPF and its +//! limitations, see the main Solana documentation for [on-chain programs][ocp]. +//! +//! [ocp]: https://solana.com/docs/programs +//! +//! # Core data types +//! +//! - [`Pubkey`] — The address of a [Solana account][acc]. Some account +//! addresses are [ed25519] public keys, with corresponding secret keys that +//! are managed off-chain. Often, though, account addresses do not have +//! corresponding secret keys — as with [_program derived +//! addresses_][pdas] — or the secret key is not relevant to the +//! operation of a program, and may have even been disposed of. As running +//! Solana programs can not safely create or manage secret keys, the full +//! [`Keypair`] is not defined in `solana-program` but in `solana-sdk`. +//! - [`Hash`] — A cryptographic hash. Used to uniquely identify blocks, +//! and also for general purpose hashing. +//! - [`AccountInfo`] — A description of a single Solana account. All accounts +//! that might be accessed by a program invocation are provided to the program +//! entrypoint as `AccountInfo`. +//! - [`Instruction`] — A directive telling the runtime to execute a program, +//! passing it a set of accounts and program-specific data. +//! - [`ProgramError`] and [`ProgramResult`] — The error type that all programs +//! must return, reported to the runtime as a `u64`. +//! - [`Sol`] — The Solana native token type, with conversions to and from +//! [_lamports_], the smallest fractional unit of SOL, in the [`native_token`] +//! module. +//! +//! [acc]: https://solana.com/docs/core/accounts +//! [`Pubkey`]: pubkey::Pubkey +//! [`Hash`]: hash::Hash +//! [`Instruction`]: instruction::Instruction +//! [`AccountInfo`]: account_info::AccountInfo +//! [`ProgramError`]: program_error::ProgramError +//! [`ProgramResult`]: entrypoint::ProgramResult +//! [ed25519]: https://ed25519.cr.yp.to/ +//! [`Keypair`]: https://docs.rs/solana-sdk/latest/solana_sdk/signer/keypair/struct.Keypair.html +//! [SHA-256]: https://en.wikipedia.org/wiki/SHA-2 +//! [`Sol`]: native_token::Sol +//! [_lamports_]: https://solana.com/docs/intro#what-are-sols +//! +//! # Serialization +//! +//! Within the Solana runtime, programs, and network, at least three different +//! serialization formats are used, and `solana-program` provides access to +//! those needed by programs. +//! +//! In user-written Solana program code, serialization is primarily used for +//! accessing [`AccountInfo`] data and [`Instruction`] data, both of which are +//! program-specific binary data. Every program is free to decide their own +//! serialization format, but data received from other sources — +//! [sysvars][sysvar] for example — must be deserialized using the methods +//! indicated by the documentation for that data or data type. +//! +//! [`AccountInfo`]: account_info::AccountInfo +//! [`Instruction`]: instruction::Instruction +//! +//! The three serialization formats in use in Solana are: +//! +//! - __[Borsh]__, a compact and well-specified format developed by the [NEAR] +//! project, suitable for use in protocol definitions and for archival storage. +//! It has a [Rust implementation][brust] and a [JavaScript implementation][bjs] +//! and is recommended for all purposes. +//! +//! Users need to import the [`borsh`] crate themselves — it is not +//! re-exported by `solana-program`, though this crate provides several useful +//! utilities in its [`borsh1` module][borshmod] that are not available in the +//! `borsh` library. +//! +//! The [`Instruction::new_with_borsh`] function creates an `Instruction` by +//! serializing a value with borsh. +//! +//! [Borsh]: https://borsh.io/ +//! [NEAR]: https://near.org/ +//! [brust]: https://docs.rs/borsh +//! [bjs]: https://github.com/near/borsh-js +//! [`borsh`]: https://docs.rs/borsh +//! [borshmod]: crate::borsh1 +//! [`Instruction::new_with_borsh`]: instruction::Instruction::new_with_borsh +//! +//! - __[Bincode]__, a compact serialization format that implements the [Serde] +//! Rust APIs. As it does not have a specification nor a JavaScript +//! implementation, and uses more CPU than borsh, it is not recommend for new +//! code. +//! +//! Many system program and native program instructions are serialized with +//! bincode, and it is used for other purposes in the runtime. In these cases +//! Rust programmers are generally not directly exposed to the encoding format +//! as it is hidden behind APIs. +//! +//! The [`Instruction::new_with_bincode`] function creates an `Instruction` by +//! serializing a value with bincode. +//! +//! [Bincode]: https://docs.rs/bincode +//! [Serde]: https://serde.rs/ +//! [`Instruction::new_with_bincode`]: instruction::Instruction::new_with_bincode +//! +//! - __[`Pack`]__, a Solana-specific serialization API that is used by many +//! older programs in the [Solana Program Library][spl] to define their +//! account format. It is difficult to implement and does not define a +//! language-independent serialization format. It is not generally recommended +//! for new code. +//! +//! [`Pack`]: https://docs.rs/solana-program-pack/latest/trait.Pack.html +//! +//! Developers should carefully consider the CPU cost of serialization, balanced +//! against the need for correctness and ease of use: off-the-shelf +//! serialization formats tend to be more expensive than carefully hand-written +//! application-specific formats; but application-specific formats are more +//! difficult to ensure the correctness of, and to provide multi-language +//! implementations for. It is not uncommon for programs to pack and unpack +//! their data with hand-written code. +//! +//! # Cross-program instruction execution +//! +//! Solana programs may call other programs, termed [_cross-program +//! invocation_][cpi] (CPI), with the [`invoke`] and [`invoke_signed`] +//! functions. When calling another program the caller must provide the +//! [`Instruction`] to be invoked, as well as the [`AccountInfo`] for every +//! account required by the instruction. Because the only way for a program to +//! acquire `AccountInfo` values is by receiving them from the runtime at the +//! [program entrypoint][entrypoint!], any account required by the callee +//! program must transitively be required by the caller program, and provided by +//! _its_ caller. +//! +//! [`invoke`]: program::invoke +//! [`invoke_signed`]: program::invoke_signed +//! [cpi]: https://solana.com/docs/core/cpi +//! +//! A simple example of transferring lamports via CPI: +//! +//! ``` +//! use solana_account_info::{next_account_info, AccountInfo}; +//! use solana_program_entrypoint::entrypoint; +//! use solana_program_error::ProgramResult; +//! use solana_cpi::invoke; +//! use solana_pubkey::Pubkey; +//! use solana_system_interface::instruction::transfer; +//! +//! entrypoint!(process_instruction); +//! +//! fn process_instruction( +//! program_id: &Pubkey, +//! accounts: &[AccountInfo], +//! instruction_data: &[u8], +//! ) -> ProgramResult { +//! let account_info_iter = &mut accounts.iter(); +//! +//! let payer = next_account_info(account_info_iter)?; +//! let recipient = next_account_info(account_info_iter)?; +//! +//! assert!(payer.is_writable); +//! assert!(payer.is_signer); +//! assert!(recipient.is_writable); +//! +//! let lamports = 1000000; +//! +//! invoke( +//! &transfer(payer.key, recipient.key, lamports), +//! &[payer.clone(), recipient.clone()], +//! ) +//! } +//! ``` +//! +//! Solana also includes a mechanism to let programs control and sign for +//! accounts without needing to protect a corresponding secret key, called +//! [_program derived addresses_][pdas]. PDAs are derived with the +//! [`Pubkey::find_program_address`] function. With a PDA, a program can call +//! `invoke_signed` to call another program while virtually "signing" for the +//! PDA. +//! +//! [pdas]: https://solana.com/docs/core/cpi#program-derived-addresses +//! [`Pubkey::find_program_address`]: pubkey::Pubkey::find_program_address +//! +//! A simple example of creating an account for a PDA: +//! +//! ``` +//! use solana_account_info::{next_account_info, AccountInfo}; +//! use solana_program_entrypoint::entrypoint; +//! use solana_program_error::ProgramResult; +//! use solana_cpi::invoke_signed; +//! use solana_pubkey::Pubkey; +//! use solana_system_interface::instruction::create_account; +//! +//! entrypoint!(process_instruction); +//! +//! fn process_instruction( +//! program_id: &Pubkey, +//! accounts: &[AccountInfo], +//! instruction_data: &[u8], +//! ) -> ProgramResult { +//! let account_info_iter = &mut accounts.iter(); +//! let payer = next_account_info(account_info_iter)?; +//! let vault_pda = next_account_info(account_info_iter)?; +//! let system_program = next_account_info(account_info_iter)?; +//! +//! assert!(payer.is_writable); +//! assert!(payer.is_signer); +//! assert!(vault_pda.is_writable); +//! assert_eq!(vault_pda.owner, &solana_system_interface::program::ID); +//! assert!(solana_system_interface::program::check_id(system_program.key)); +//! +//! let vault_bump_seed = instruction_data[0]; +//! let vault_seeds = &[b"vault", payer.key.as_ref(), &[vault_bump_seed]]; +//! let expected_vault_pda = Pubkey::create_program_address(vault_seeds, program_id)?; +//! +//! assert_eq!(vault_pda.key, &expected_vault_pda); +//! +//! let lamports = 10000000; +//! let vault_size = 16; +//! +//! invoke_signed( +//! &create_account( +//! &payer.key, +//! &vault_pda.key, +//! lamports, +//! vault_size, +//! &program_id, +//! ), +//! &[ +//! payer.clone(), +//! vault_pda.clone(), +//! ], +//! &[ +//! &[ +//! b"vault", +//! payer.key.as_ref(), +//! &[vault_bump_seed], +//! ], +//! ] +//! )?; +//! Ok(()) +//! } +//! ``` +//! +//! # Native programs +//! +//! Some solana programs are [_native programs_][np2], running native machine +//! code that is distributed with the runtime, with well-known program IDs. +//! +//! [np2]: https://docs.solanalabs.com/runtime/programs +//! +//! Some native programs can be [invoked][cpi] by other programs, but some can +//! only be executed as "top-level" instructions included by off-chain clients +//! in a [`Transaction`]. +//! +//! [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html +//! +//! This crate defines the program IDs for most native programs. Even though +//! some native programs cannot be invoked by other programs, a Solana program +//! may need access to their program IDs. For example, a program may need to +//! verify that an ed25519 signature verification instruction was included in +//! the same transaction as its own instruction. For many native programs, this +//! crate also defines enums that represent the instructions they process, and +//! constructors for building the instructions. +//! +//! Locations of program IDs and instruction constructors are noted in the list +//! below, as well as whether they are invokable by other programs. +//! +//! While some native programs have been active since the genesis block, others +//! are activated dynamically after a specific [slot], and some are not yet +//! active. This documentation does not distinguish which native programs are +//! active on any particular network. The `solana feature status` CLI command +//! can help in determining active features. +//! +//! [slot]: https://solana.com/docs/terminology#slot +//! +//! Native programs important to Solana program authors include: +//! +//! - __System Program__: Creates new accounts, allocates account data, assigns +//! accounts to owning programs, transfers lamports from System Program owned +//! accounts and pays transaction fees. +//! - ID: [`solana_system_interface::program::ID`](https://docs.rs/solana-system-interface/latest/solana_system_interface/program/constant.ID.html) +//! - Instruction: [`solana_system_interface::instruction`](https://docs.rs/solana-system-interface/latest/solana_system_interface/instruction/index.html) +//! - Invokable by programs? yes +//! +//! - __Compute Budget Program__: Requests additional CPU or memory resources +//! for a transaction. This program does nothing when called from another +//! program. +//! - ID: [`solana_compute_budget_interface::ID`](https://docs.rs/solana-compute-budget-interface/latest/solana_compute_budget_interface/constant.ID.html) +//! - Instruction: [`solana_compute_budget_interface::ComputeBudgetInstruction`](https://docs.rs/solana-compute-budget-interface/latest/solana_compute_budget_interface/enum.ComputeBudgetInstruction.html) +//! - Invokable by programs? no +//! +//! - __ed25519 Program__: Verifies an ed25519 signature. +//! - ID: [`solana_sdk_ids::ed25519_program::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/ed25519_program/constant.ID.html) +//! - Instruction: [`solana_ed25519_program::new_ed25519_instruction_with_signature`](https://docs.rs/solana-ed25519-program/latest/solana_ed25519_program/fn.new_ed25519_instruction_with_signature.html) +//! - Invokable by programs? no +//! +//! - __secp256k1 Program__: Verifies secp256k1 public key recovery operations. +//! - ID: [`solana_sdk_ids::secp256k1_program::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/secp256k1_program/constant.ID.html) +//! - Instruction: [`solana_secp256k1_program::new_secp256k1_instruction_with_signature`](https://docs.rs/solana-secp256k1-program/latest/solana_secp256k1_program/fn.new_secp256k1_instruction_with_signature.html) +//! - Invokable by programs? no +//! +//! - __BPF Loader__: Deploys, and executes immutable programs on the chain. +//! - ID: [`solana_sdk_ids::bpf_loader::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/bpf_loader/constant.ID.html) +//! - Instruction: [`solana_loader_v2_interface::instruction`](https://docs.rs/solana-loader-v2-interface/latest/solana_loader_v2_interface/instruction/index.html) +//! - Invokable by programs? yes +//! +//! - __Upgradable BPF Loader__: Deploys, upgrades, and executes upgradable +//! programs on the chain. +//! - ID: [`solana_sdk_ids::bpf_loader_upgradeable::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/bpf_loader_upgradeable/constant.ID.html) +//! - Instruction: [`solana_loader_v3_interface::instruction`](https://docs.rs/solana-loader-v3-interface/latest/solana_loader_v3_interface/instruction/index.html) +//! - Invokable by programs? yes +//! +//! - __Deprecated BPF Loader__: Deploys, and executes immutable programs on the +//! chain. +//! - ID: [`solana_sdk_ids::bpf_loader_deprecated::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/bpf_loader_deprecated/constant.ID.html) +//! - Instruction: [`solana_loader_v2_interface::instruction`](https://docs.rs/solana-loader-v2-interface/latest/solana_loader_v2_interface/instruction/index.html) +//! - Invokable by programs? yes +//! +//! [lut]: https://docs.solanalabs.com/proposals/versioned-transactions + +#![allow(incomplete_features)] +#![cfg_attr(feature = "frozen-abi", feature(specialization))] +#![cfg_attr(docsrs, feature(doc_cfg))] + +// Allows macro expansion of `use ::solana_program::*` to work within this crate +extern crate self as solana_program; + +pub mod bpf_loader; +pub mod bpf_loader_deprecated; +pub mod compute_units; +pub mod ed25519_program; +pub mod entrypoint_deprecated; +pub mod epoch_schedule; +pub use solana_epoch_stake as epoch_stake; +pub mod hash; +pub mod incinerator; +pub mod instruction; +pub mod lamports; +pub mod log; +pub mod program; +pub mod program_error; +pub mod secp256k1_program; +pub mod slot_hashes; +pub mod slot_history; +pub mod syscalls; +pub mod sysvar; + +#[deprecated(since = "2.2.0", note = "Use `solana-big-mod-exp` crate instead")] +pub use solana_big_mod_exp as big_mod_exp; +#[deprecated(since = "2.2.0", note = "Use `solana-blake3-hasher` crate instead")] +pub use solana_blake3_hasher as blake3; +#[cfg(feature = "borsh")] +#[deprecated(since = "2.1.0", note = "Use `solana-borsh` crate instead")] +pub use solana_borsh::v1 as borsh1; +#[deprecated(since = "2.1.0", note = "Use `solana-epoch-rewards` crate instead")] +pub use solana_epoch_rewards as epoch_rewards; +#[deprecated(since = "2.1.0", note = "Use `solana-fee-calculator` crate instead")] +pub use solana_fee_calculator as fee_calculator; +#[deprecated(since = "2.2.0", note = "Use `solana-keccak-hasher` crate instead")] +pub use solana_keccak_hasher as keccak; +#[deprecated(since = "2.1.0", note = "Use `solana-last-restart-slot` crate instead")] +pub use solana_last_restart_slot as last_restart_slot; +#[deprecated(since = "2.1.0", note = "Use `solana-program-memory` crate instead")] +pub use solana_program_memory as program_memory; +#[deprecated(since = "2.1.0", note = "Use `solana-program-pack` crate instead")] +pub use solana_program_pack as program_pack; +#[deprecated(since = "2.1.0", note = "Use `solana-secp256k1-recover` crate instead")] +pub use solana_secp256k1_recover as secp256k1_recover; +#[deprecated(since = "2.1.0", note = "Use `solana-serde-varint` crate instead")] +pub use solana_serde_varint as serde_varint; +#[deprecated(since = "2.1.0", note = "Use `solana-serialize-utils` crate instead")] +pub use solana_serialize_utils as serialize_utils; +#[deprecated(since = "2.1.0", note = "Use `solana-short-vec` crate instead")] +pub use solana_short_vec as short_vec; +#[deprecated(since = "2.1.0", note = "Use `solana-stable-layout` crate instead")] +pub use solana_stable_layout as stable_layout; +#[cfg(not(target_os = "solana"))] +pub use solana_sysvar::program_stubs; +pub use { + solana_account_info::{self as account_info, debug_account_data}, + solana_clock as clock, + solana_msg::msg, + solana_native_token as native_token, + solana_program_entrypoint::{ + self as entrypoint, custom_heap_default, custom_panic_default, entrypoint, + entrypoint_no_alloc, + }, + solana_program_option as program_option, solana_pubkey as pubkey, solana_rent as rent, + solana_sysvar::impl_sysvar_get, +}; +/// The [config native program][np]. +/// +/// [np]: https://docs.solanalabs.com/runtime/programs#config-program +pub mod config { + pub mod program { + pub use solana_sdk_ids::config::{check_id, id, ID}; + } +} + +pub use solana_pubkey::{declare_deprecated_id, declare_id, pubkey}; +#[deprecated(since = "2.1.0", note = "Use `solana-sysvar-id` crate instead")] +pub use solana_sysvar_id::{declare_deprecated_sysvar_id, declare_sysvar_id}; + +/// Convenience macro for doing integer division where the operation's safety +/// can be checked at compile-time. +/// +/// Since `unchecked_div_by_const!()` is supposed to fail at compile-time, abuse +/// doctests to cover failure modes +/// +/// # Examples +/// +/// Literal denominator div-by-zero fails: +/// +/// ```compile_fail +/// # use solana_program::unchecked_div_by_const; +/// # fn main() { +/// let _ = unchecked_div_by_const!(10, 0); +/// # } +/// ``` +/// +/// Const denominator div-by-zero fails: +/// +/// ```compile_fail +/// # use solana_program::unchecked_div_by_const; +/// # fn main() { +/// const D: u64 = 0; +/// let _ = unchecked_div_by_const!(10, D); +/// # } +/// ``` +/// +/// Non-const denominator fails: +/// +/// ```compile_fail +/// # use solana_program::unchecked_div_by_const; +/// # fn main() { +/// let d = 0; +/// let _ = unchecked_div_by_const!(10, d); +/// # } +/// ``` +/// +/// Literal denominator div-by-zero fails: +/// +/// ```compile_fail +/// # use solana_program::unchecked_div_by_const; +/// # fn main() { +/// const N: u64 = 10; +/// let _ = unchecked_div_by_const!(N, 0); +/// # } +/// ``` +/// +/// Const denominator div-by-zero fails: +/// +/// ```compile_fail +/// # use solana_program::unchecked_div_by_const; +/// # fn main() { +/// const N: u64 = 10; +/// const D: u64 = 0; +/// let _ = unchecked_div_by_const!(N, D); +/// # } +/// ``` +/// +/// Non-const denominator fails: +/// +/// ```compile_fail +/// # use solana_program::unchecked_div_by_const; +/// # fn main() { +/// # const N: u64 = 10; +/// let d = 0; +/// let _ = unchecked_div_by_const!(N, d); +/// # } +/// ``` +/// +/// Literal denominator div-by-zero fails: +/// +/// ```compile_fail +/// # use solana_program::unchecked_div_by_const; +/// # fn main() { +/// let n = 10; +/// let _ = unchecked_div_by_const!(n, 0); +/// # } +/// ``` +/// +/// Const denominator div-by-zero fails: +/// +/// ```compile_fail +/// # use solana_program::unchecked_div_by_const; +/// # fn main() { +/// let n = 10; +/// const D: u64 = 0; +/// let _ = unchecked_div_by_const!(n, D); +/// # } +/// ``` +/// +/// Non-const denominator fails: +/// +/// ```compile_fail +/// # use solana_program::unchecked_div_by_const; +/// # fn main() { +/// let n = 10; +/// let d = 0; +/// let _ = unchecked_div_by_const!(n, d); +/// # } +/// ``` +#[macro_export] +macro_rules! unchecked_div_by_const { + ($num:expr, $den:expr) => {{ + // Ensure the denominator is compile-time constant + let _ = [(); ($den - $den) as usize]; + // Compile-time constant integer div-by-zero passes for some reason + // when invoked from a compilation unit other than that where this + // macro is defined. Do an explicit zero-check for now. Sorry about the + // ugly error messages! + // https://users.rust-lang.org/t/unexpected-behavior-of-compile-time-integer-div-by-zero-check-in-declarative-macro/56718 + let _ = [(); ($den as usize) - 1]; + #[allow(clippy::arithmetic_side_effects)] + let quotient = $num / $den; + quotient + }}; +} + +// This re-export is purposefully listed after all other exports: because of an +// interaction within rustdoc between the reexports inside this module of +// `solana_program`'s top-level modules, and `solana_sdk`'s glob re-export of +// `solana_program`'s top-level modules, if this re-export is not lexically last +// rustdoc fails to generate documentation for the re-exports within +// `solana_sdk`. +#[deprecated(since = "2.2.0", note = "Use solana-example-mocks instead")] +#[cfg(not(target_os = "solana"))] +pub use solana_example_mocks as example_mocks; + +#[cfg(test)] +mod tests { + #[test] + fn test_unchecked_div_by_const() { + const D: u64 = 2; + const N: u64 = 10; + let n = 10; + assert_eq!(unchecked_div_by_const!(10, 2), 5); + assert_eq!(unchecked_div_by_const!(N, 2), 5); + assert_eq!(unchecked_div_by_const!(n, 2), 5); + assert_eq!(unchecked_div_by_const!(10, D), 5); + assert_eq!(unchecked_div_by_const!(N, D), 5); + assert_eq!(unchecked_div_by_const!(n, D), 5); + } +} diff --git a/sp4/solana-program-4.0.0/src/log.rs b/sp4/solana-program-4.0.0/src/log.rs new file mode 100644 index 00000000..049e286c --- /dev/null +++ b/sp4/solana-program-4.0.0/src/log.rs @@ -0,0 +1,101 @@ +//! Logging utilities for Rust-based Solana programs. +//! +//! Logging is the main mechanism for getting debugging information out of +//! running Solana programs, and there are several functions available for doing +//! so efficiently, depending on the type of data being logged. +//! +//! The most common way to emit logs is through the [`msg!`] macro, which logs +//! simple strings, as well as [formatted strings][fs]. +//! +//! [`msg!`]: crate::msg! +//! [fs]: https://doc.rust-lang.org/std/fmt/ +//! +//! Logs can be viewed in multiple ways: +//! +//! - The `solana logs` command displays logs for all transactions executed on a +//! network. Note though that transactions that fail during pre-flight +//! simulation are not displayed here. +//! - When submitting transactions via [`RpcClient`], if Rust's own logging is +//! active then the `solana_rpc_client` crate logs at the "debug" level any logs +//! for transactions that failed during simulation. If using [`env_logger`] +//! these logs can be activated by setting `RUST_LOG=solana_rpc_client=debug`. +//! - Logs can be retrieved from a finalized transaction by calling +//! [`RpcClient::get_transaction`]. +//! - Block explorers may display logs. +//! +//! [`RpcClient`]: https://docs.rs/solana-rpc-client/latest/solana_rpc_client/rpc_client/struct.RpcClient.html +//! [`env_logger`]: https://docs.rs/env_logger +//! [`RpcClient::get_transaction`]: https://docs.rs/solana-rpc-client/latest/solana_rpc_client/rpc_client/struct.RpcClient.html#method.get_transaction +//! +//! While most logging functions are defined in this module, [`Pubkey`]s can +//! also be efficiently logged with the [`Pubkey::log`] function. +//! +//! [`Pubkey`]: crate::pubkey::Pubkey +//! [`Pubkey::log`]: crate::pubkey::Pubkey::log + +use crate::account_info::AccountInfo; +pub use solana_msg::{msg, sol_log}; + +/// Print 64-bit values represented as hexadecimal to the log. +#[inline] +pub fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { + #[cfg(target_os = "solana")] + unsafe { + crate::syscalls::sol_log_64_(arg1, arg2, arg3, arg4, arg5); + } + + #[cfg(not(target_os = "solana"))] + crate::program_stubs::sol_log_64(arg1, arg2, arg3, arg4, arg5); +} + +/// Print some slices as base64. +pub fn sol_log_data(data: &[&[u8]]) { + #[cfg(target_os = "solana")] + unsafe { + crate::syscalls::sol_log_data(data as *const _ as *const u8, data.len() as u64) + }; + + #[cfg(not(target_os = "solana"))] + crate::program_stubs::sol_log_data(data); +} + +/// Print the hexadecimal representation of a slice. +pub fn sol_log_slice(slice: &[u8]) { + for (i, s) in slice.iter().enumerate() { + sol_log_64(0, 0, 0, i as u64, *s as u64); + } +} + +/// Print the hexadecimal representation of the program's input parameters. +/// +/// - `accounts` - A slice of [`AccountInfo`]. +/// - `data` - The instruction data. +pub fn sol_log_params(accounts: &[AccountInfo], data: &[u8]) { + for (i, account) in accounts.iter().enumerate() { + msg!("AccountInfo"); + sol_log_64(0, 0, 0, 0, i as u64); + msg!("- Is signer"); + sol_log_64(0, 0, 0, 0, account.is_signer as u64); + msg!("- Key"); + account.key.log(); + msg!("- Lamports"); + sol_log_64(0, 0, 0, 0, account.lamports()); + msg!("- Account data length"); + sol_log_64(0, 0, 0, 0, account.data_len() as u64); + msg!("- Owner"); + account.owner.log(); + } + msg!("Instruction data"); + sol_log_slice(data); +} + +/// Print the remaining compute units available to the program. +#[inline] +pub fn sol_log_compute_units() { + #[cfg(target_os = "solana")] + unsafe { + crate::syscalls::sol_log_compute_units_(); + } + #[cfg(not(target_os = "solana"))] + crate::program_stubs::sol_log_compute_units(); +} diff --git a/sp4/solana-program-4.0.0/src/program.rs b/sp4/solana-program-4.0.0/src/program.rs new file mode 100644 index 00000000..6816a805 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/program.rs @@ -0,0 +1,215 @@ +//! Wrappers around [`solana-cpi`] with support for overwriting +//! syscall stubs +//! +//! Solana programs may call other programs, termed [_cross-program +//! invocations_][cpi] (CPI), with the [`invoke`] and [`invoke_signed`] +//! functions. +//! +//! [`solana-cpi`]: https://docs.rs/solana-cpi/latest/solana_cpi/ +//! [`invoke`]: invoke +//! [`invoke_signed`]: invoke_signed +//! [cpi]: https://solana.com/docs/core/cpi + +use crate::{ + account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey, + stable_layout::stable_instruction::StableInstruction, +}; +pub use solana_cpi::MAX_RETURN_DATA; + +/// Like [`solana_cpi::invoke`], but with support +/// for overwriting the `sol_invoke_signed` syscall stub. +/// +/// [`solana_cpi::invoke`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html +pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult { + invoke_signed(instruction, account_infos, &[]) +} + +/// Like [`solana_cpi::invoke_unchecked`], but with support +/// for overwriting the `sol_invoke_signed` syscall stub. +/// +/// [`solana_cpi::invoke_unchecked`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_unchecked.html +/// +/// # Safety +/// +/// __This function is incorrectly missing an `unsafe` declaration.__ +/// +/// If any of the writable accounts passed to the callee contain data that is +/// borrowed within the calling program, and that data is written to by the +/// callee, then Rust's aliasing rules will be violated and cause undefined +/// behavior. +pub fn invoke_unchecked(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult { + invoke_signed_unchecked(instruction, account_infos, &[]) +} + +/// Like [`solana_cpi::invoke_signed`], but with support +/// for overwriting the `sol_invoke_signed` syscall stub. +/// +/// [`solana_cpi::invoke_signed`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed.html +pub fn invoke_signed( + instruction: &Instruction, + account_infos: &[AccountInfo], + signers_seeds: &[&[&[u8]]], +) -> ProgramResult { + // Check that the account RefCells are consistent with the request + for account_meta in instruction.accounts.iter() { + for account_info in account_infos.iter() { + if account_meta.pubkey == *account_info.key { + if account_meta.is_writable { + let _ = account_info.try_borrow_mut_lamports()?; + let _ = account_info.try_borrow_mut_data()?; + } else { + let _ = account_info.try_borrow_lamports()?; + let _ = account_info.try_borrow_data()?; + } + break; + } + } + } + + invoke_signed_unchecked(instruction, account_infos, signers_seeds) +} + +/// Like [`solana_cpi::invoke_signed_unchecked`], but with support +/// for overwriting the `sol_invoke_signed` syscall stub. +/// +/// [`solana_cpi::invoke_signed_unchecked`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed_unchecked.html +/// +/// # Safety +/// +/// __This function is incorrectly missing an `unsafe` declaration.__ +/// +/// If any of the writable accounts passed to the callee contain data that is +/// borrowed within the calling program, and that data is written to by the +/// callee, then Rust's aliasing rules will be violated and cause undefined +/// behavior. +pub fn invoke_signed_unchecked( + instruction: &Instruction, + account_infos: &[AccountInfo], + signers_seeds: &[&[&[u8]]], +) -> ProgramResult { + #[cfg(target_os = "solana")] + { + solana_cpi::invoke_signed_unchecked(instruction, account_infos, signers_seeds) + } + + #[cfg(not(target_os = "solana"))] + crate::program_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds) +} + +/// Like [`solana_cpi::set_return_data`], but with support +/// for overwriting the `sol_set_return_data` syscall stub. +/// +/// [`solana_cpi::set_return_data`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.set_return_data.html +pub fn set_return_data(data: &[u8]) { + #[cfg(target_os = "solana")] + { + solana_cpi::set_return_data(data); + } + + #[cfg(not(target_os = "solana"))] + crate::program_stubs::sol_set_return_data(data) +} + +/// Like [`solana_cpi::get_return_data`], but with support +/// for overwriting the `sol_get_return_data` syscall stub. +/// +/// [`solana_cpi::get_return_data`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.get_return_data.html +pub fn get_return_data() -> Option<(Pubkey, Vec)> { + #[cfg(target_os = "solana")] + { + solana_cpi::get_return_data() + } + + #[cfg(not(target_os = "solana"))] + crate::program_stubs::sol_get_return_data() +} + +/// Do sanity checks of type layout. +#[doc(hidden)] +#[allow(clippy::arithmetic_side_effects)] +pub fn check_type_assumptions() { + extern crate memoffset; + use { + crate::instruction::AccountMeta, + memoffset::offset_of, + std::{ + mem::{align_of, size_of}, + str::FromStr, + }, + }; + + // Code in this file assumes that u64 and usize are the same + assert_eq!(size_of::(), size_of::()); + // Code in this file assumes that u8 is byte aligned + assert_eq!(1, align_of::()); + + // Enforce Instruction layout + { + assert_eq!(size_of::(), 32 + 1 + 1); + + let pubkey1 = Pubkey::from_str("J9PYCcoKusHyKRMXnBL17VTXC3MVETyqBG2KyLXVv6Ai").unwrap(); + let pubkey2 = Pubkey::from_str("Hvy4GHgPToZNoENTKjC4mJqpzWWjgTwXrFufKfxYiKkV").unwrap(); + let pubkey3 = Pubkey::from_str("JDMyRL8rCkae7maCSv47upNuBMFd3Mgos1fz2AvYzVzY").unwrap(); + let account_meta1 = AccountMeta { + pubkey: pubkey2, + is_signer: true, + is_writable: false, + }; + let account_meta2 = AccountMeta { + pubkey: pubkey3, + is_signer: false, + is_writable: true, + }; + let data = vec![1, 2, 3, 4, 5]; + let instruction = Instruction { + program_id: pubkey1, + accounts: vec![account_meta1.clone(), account_meta2.clone()], + data: data.clone(), + }; + let instruction = StableInstruction::from(instruction); + let instruction_addr = &instruction as *const _ as u64; + + // program id + assert_eq!(offset_of!(StableInstruction, program_id), 48); + let pubkey_ptr = (instruction_addr + 48) as *const Pubkey; + unsafe { + assert_eq!(*pubkey_ptr, pubkey1); + } + + // accounts + assert_eq!(offset_of!(StableInstruction, accounts), 0); + let accounts_ptr = (instruction_addr) as *const *const AccountMeta; + let accounts_cap = (instruction_addr + 8) as *const usize; + let accounts_len = (instruction_addr + 16) as *const usize; + unsafe { + assert_eq!(*accounts_cap, 2); + assert_eq!(*accounts_len, 2); + let account_meta_ptr = *accounts_ptr; + assert_eq!(*account_meta_ptr, account_meta1); + assert_eq!(*(account_meta_ptr.offset(1)), account_meta2); + } + + // data + assert_eq!(offset_of!(StableInstruction, data), 24); + let data_ptr = (instruction_addr + 24) as *const *const [u8; 5]; + let data_cap = (instruction_addr + 24 + 8) as *const usize; + let data_len = (instruction_addr + 24 + 16) as *const usize; + unsafe { + assert_eq!(*data_cap, 5); + + assert_eq!(*data_len, 5); + let u8_ptr = *data_ptr; + assert_eq!(*u8_ptr, data[..]); + } + } + + solana_account_info::check_type_assumptions(); +} + +#[cfg(test)] +mod tests { + #[test] + fn test_check_type_assumptions() { + super::check_type_assumptions() + } +} diff --git a/sp4/solana-program-4.0.0/src/program_error.rs b/sp4/solana-program-4.0.0/src/program_error.rs new file mode 100644 index 00000000..975a5e31 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/program_error.rs @@ -0,0 +1,14 @@ +pub use { + solana_instruction_error::{ + ACCOUNT_ALREADY_INITIALIZED, ACCOUNT_BORROW_FAILED, ACCOUNT_DATA_TOO_SMALL, + ACCOUNT_NOT_RENT_EXEMPT, ARITHMETIC_OVERFLOW, BORSH_IO_ERROR, + BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS, CUSTOM_ZERO, ILLEGAL_OWNER, IMMUTABLE, + INCORRECT_AUTHORITY, INCORRECT_PROGRAM_ID, INSUFFICIENT_FUNDS, INVALID_ACCOUNT_DATA, + INVALID_ACCOUNT_DATA_REALLOC, INVALID_ACCOUNT_OWNER, INVALID_ARGUMENT, + INVALID_INSTRUCTION_DATA, INVALID_SEEDS, MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED, + MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED, MAX_SEED_LENGTH_EXCEEDED, + MISSING_REQUIRED_SIGNATURES, NOT_ENOUGH_ACCOUNT_KEYS, UNINITIALIZED_ACCOUNT, + UNSUPPORTED_SYSVAR, + }, + solana_program_error::ProgramError, +}; diff --git a/sp4/solana-program-4.0.0/src/secp256k1_program.rs b/sp4/solana-program-4.0.0/src/secp256k1_program.rs new file mode 100644 index 00000000..54a9f836 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/secp256k1_program.rs @@ -0,0 +1,9 @@ +//! The [secp256k1 native program][np]. +//! +//! [np]: https://docs.solanalabs.com/runtime/programs#secp256k1-program +//! +//! Constructors for secp256k1 program instructions, and documentation on the +//! program's usage can be found in [`solana_sdk::secp256k1_instruction`]. +//! +//! [`solana_sdk::secp256k1_instruction`]: https://docs.rs/solana-sdk/latest/solana_sdk/secp256k1_instruction/index.html +pub use solana_sdk_ids::secp256k1_program::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/slot_hashes.rs b/sp4/solana-program-4.0.0/src/slot_hashes.rs new file mode 100644 index 00000000..7113b234 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/slot_hashes.rs @@ -0,0 +1 @@ +pub use {solana_clock::Slot, solana_slot_hashes::*}; diff --git a/sp4/solana-program-4.0.0/src/slot_history.rs b/sp4/solana-program-4.0.0/src/slot_history.rs new file mode 100644 index 00000000..982c0cac --- /dev/null +++ b/sp4/solana-program-4.0.0/src/slot_history.rs @@ -0,0 +1,2 @@ +#[deprecated(since = "2.1.0", note = "Use `solana-slot-history` crate instead")] +pub use {solana_clock::Slot, solana_slot_history::*}; diff --git a/sp4/solana-program-4.0.0/src/syscalls/definitions.rs b/sp4/solana-program-4.0.0/src/syscalls/definitions.rs new file mode 100644 index 00000000..1eba1111 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/syscalls/definitions.rs @@ -0,0 +1,39 @@ +#[deprecated(since = "2.1.0", note = "Use `solana_cpi::syscalls` instead")] +pub use solana_cpi::syscalls::{ + sol_get_return_data, sol_invoke_signed_c, sol_invoke_signed_rust, sol_set_return_data, +}; +#[deprecated( + since = "2.2.0", + note = "Use `solana_define_syscall::definitions` instead" +)] +pub use solana_define_syscall::definitions::{ + sol_alt_bn128_compression, sol_alt_bn128_group_op, sol_big_mod_exp, sol_blake3, + sol_curve_group_op, sol_curve_multiscalar_mul, sol_curve_pairing_map, sol_curve_validate_point, + sol_get_clock_sysvar, sol_get_epoch_rewards_sysvar, sol_get_epoch_schedule_sysvar, + sol_get_epoch_stake, sol_get_fees_sysvar, sol_get_last_restart_slot, sol_get_rent_sysvar, + sol_get_sysvar, sol_keccak256, sol_remaining_compute_units, +}; +#[cfg(target_feature = "static-syscalls")] +pub use solana_define_syscall::sys_hash; +#[deprecated(since = "2.1.0", note = "Use `solana_instruction::syscalls` instead")] +pub use solana_instruction::syscalls::{ + sol_get_processed_sibling_instruction, sol_get_stack_height, +}; +#[deprecated(since = "2.1.0", note = "Use `solana_msg::syscalls` instead.")] +pub use solana_msg::syscalls::{sol_log_, sol_log_64_, sol_log_compute_units_, sol_log_data}; +#[deprecated( + since = "2.1.0", + note = "Use `solana_program_memory::syscalls` instead" +)] +pub use solana_program_memory::syscalls::{sol_memcmp_, sol_memcpy_, sol_memmove_, sol_memset_}; +#[deprecated(since = "2.1.0", note = "Use `solana_pubkey::syscalls` instead")] +pub use solana_pubkey::syscalls::{ + sol_create_program_address, sol_log_pubkey, sol_try_find_program_address, +}; +#[deprecated( + since = "2.1.0", + note = "Use `solana_secp256k1_recover::sol_secp256k1_recover` instead" +)] +pub use solana_secp256k1_recover::sol_secp256k1_recover; +#[deprecated(since = "2.1.0", note = "Use solana_sha256_hasher::sol_sha256 instead")] +pub use solana_sha256_hasher::sol_sha256; diff --git a/sp4/solana-program-4.0.0/src/syscalls/mod.rs b/sp4/solana-program-4.0.0/src/syscalls/mod.rs new file mode 100644 index 00000000..740b5457 --- /dev/null +++ b/sp4/solana-program-4.0.0/src/syscalls/mod.rs @@ -0,0 +1,25 @@ +//! Declarations of Solana program syscalls. +//! +//! This module is mostly empty when not compiling for BPF targets. + +#[cfg(target_os = "solana")] +mod definitions; + +#[cfg(target_os = "solana")] +pub use definitions::*; + +/// Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI +/// instructions are not more limited than transaction instructions if the size +/// of transactions is doubled in the future. +pub const MAX_CPI_INSTRUCTION_DATA_LEN: u64 = 10 * 1024; + +/// Maximum CPI instruction accounts. 255 was chosen to ensure that instruction +/// accounts are always within the maximum instruction account limit for SBF +/// program instructions. +pub const MAX_CPI_INSTRUCTION_ACCOUNTS: u8 = u8::MAX; + +/// Maximum number of account info structs that can be used in a single CPI +/// invocation. A limit on account info structs is effectively the same as +/// limiting the number of unique accounts. 128 was chosen to match the max +/// number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS). +pub const MAX_CPI_ACCOUNT_INFOS: usize = 128; diff --git a/sp4/solana-program-4.0.0/src/sysvar.rs b/sp4/solana-program-4.0.0/src/sysvar.rs new file mode 100644 index 00000000..cd17415c --- /dev/null +++ b/sp4/solana-program-4.0.0/src/sysvar.rs @@ -0,0 +1,33 @@ +#[deprecated(since = "2.1.0", note = "Use `solana-sysvar-id` crate instead")] +pub use solana_sysvar_id::{declare_deprecated_sysvar_id, declare_sysvar_id, SysvarId}; +#[deprecated(since = "2.2.0", note = "Use `solana-sysvar` crate instead")] +#[allow(deprecated)] +pub use { + solana_sdk_ids::sysvar::{check_id, id, ID}, + solana_sysvar::{ + clock, epoch_rewards, epoch_schedule, fees, last_restart_slot, recent_blockhashes, rent, + rewards, slot_hashes, slot_history, Sysvar, SysvarSerialize, + }, +}; + +pub mod instructions { + #[deprecated(since = "2.2.0", note = "Use solana-instruction crate instead")] + pub use solana_instruction::{BorrowedAccountMeta, BorrowedInstruction}; + #[cfg(not(target_os = "solana"))] + #[deprecated(since = "2.2.0", note = "Use solana-instructions-sysvar crate instead")] + pub use solana_instructions_sysvar::construct_instructions_data; + #[cfg(all(not(target_os = "solana"), feature = "dev-context-only-utils"))] + #[deprecated(since = "2.2.0", note = "Use solana-instructions-sysvar crate instead")] + pub use solana_instructions_sysvar::serialize_instructions; + #[cfg(feature = "dev-context-only-utils")] + #[deprecated(since = "2.2.0", note = "Use solana-instructions-sysvar crate instead")] + pub use solana_instructions_sysvar::{deserialize_instruction, load_instruction_at}; + #[deprecated(since = "2.2.0", note = "Use solana-instructions-sysvar crate instead")] + #[allow(deprecated)] + pub use solana_instructions_sysvar::{ + get_instruction_relative, load_current_index_checked, load_instruction_at_checked, + Instructions, + }; + #[deprecated(since = "2.2.0", note = "Use solana-sdk-ids crate instead")] + pub use solana_sdk_ids::sysvar::instructions::{check_id, id, ID}; +} diff --git a/sp4/sp.crate b/sp4/sp.crate new file mode 100644 index 0000000000000000000000000000000000000000..bd7046c384b1f299a6116f5dbc2b2b6ea8a75e1a GIT binary patch literal 30262 zcmV(#K;*w4iwFn+00002|8s9_VQyh9aB^>Fa$#*PG%heMFfL2L1;ugAZ@_Zz>Le~#b(jnnf3r|0%uulF1IhX4Of?>8Ud-)gZ8=bg@P{+Isy$3ds_ z=wh;bbkdPO`LMVQ9X>sB+$ivzz>i~R*!QEsXy^s5GwSt+UXu0`Cv&rO*c&;Ixb4>? zdCX0?ykx=1x{(K%HTCG=*WY~n_pkbI(X{`c%l1FMn#FJS@{W!B-|+^X)2r`)XApec z{~zMtpR)lO~hpWO{LY@HU+rQCV&yNn`%QSwoSY68_k|cF|k(cz`Aj}*u>jzGhB)!b(r%Bdx zvUuS6SuY8*I8CB3^W7*4`@J9>^_@ZHy56IMB)yTIPUA_s;EAUPJT#q8-lpTS7e@zY zZ5^G5xAQcs^s{&D9gp_h&p{Bn>Cka~ukU0@=%m>&Y{3u+cP7p6^!|(3B{eC>im;gTw zgMQ}9x$pIcqby5?i8~k$q9lp~4u6-e;*Qw%QJf89CMoRkzkZx;Xn)|>Tkr0Dmk->? zbNhbg1a9A9nG79fz)k&L>cxIStDVt^1_%A#FpF62qh#b{{XswNWl_ZU8=GGne&;yY z`|uB0)=uaJ!)(w?f;by^OvYfC$>))mgs$&o(J&njd+d{b9FJ1E>A3yjs2>JNziszM zlW9Cl(pvMqV{gyRkDSc!rz77PB*TI04&p)B4-%IN7&vj@q5=eNj9hhCX8lnw?v27M zW7PzSlO%DNGz!^8I;ZKak`>XLU4<8mO6T0;(Xqeh&be;jBo6DGK8;d8@V&I}25gMj zWv{2c=f&|bj6&B5qe0-io~Sk1F!9*xnK$xV=Vo~w-qf4#?0sqV(6M~ZKfgB`4SZ>L z)bly-oGkQRKXRjZ7$pH4!j0ViC~%XQZO#SWD2xI>bQ|YpdCOO;>kYGW6~0Nm+QRD} z_xHW_5IXAE~G)vNgF#? zoY_YQ)&VFVV)LKuqC3$)$FnI~Pa$R#}$LYXZ^f?S0XDy}>XZ z1nwweM!k#`pcEQr)gO%l6o5GH#}UI3_d^#=0MR&12ZJz5IG4hK(>O3nC!EAxH6|N+ zI-4&pAAIWjzQ-&^>A>kl!7v#O{lxL;4;E`0`5wn=(nsgr><=$>24T-14jTJCo5txlPOq+1r!fiR?sXW?XI<5l+Lx~S{P)$&{Ia;6 zZW^1PaVf3oFOIwCVF}P~9e1Eke}CkqnI8>NgB`c$^PP)czzT33v}AvfIX>!e9Q9LF z-$9xn09zH9MSoape1GrSOOy?V5p6|Sp?JkiEgECr9S)OWA9cWs@V|`uE`lz_PVrpk z(d$M1D2QEDv&?T6(UYl>TWvG*wl0R8z4vU$WH2uYV!EHiEI$?~HVPW%0DUKm;{X{H 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 N{s%Xdak>B)0sxCJ#&rMy literal 0 HcmV?d00001 From 5521b99cedf27bcb06a569b4ff7fe2d3af7a13dd Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 04:41:22 -0300 Subject: [PATCH 11/83] feat(program): submit_spend with on-chain Groth16 verification MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The nullifier marker and the pending action share one account, seeded by the nullifier. Its existence is the replay guard — creation fails if it already exists, so a replayed proof cannot produce a second record however valid it is — and its contents are the action the proof authorised. The action binding is never transmitted. It is recomputed on-chain from the selector, the beneficiary and the relay fee, then used as the third public input, so a relay that alters any of them produces a different binding and the pairing simply fails. There is no separate field that could be checked incorrectly or forgotten. Nothing is paid out here. Settlement executes the batch so every action in an epoch lands on one timestamp and one ordering, which is what a synchronised crowd means; a per-spend payout would leak the timing the pool exists to hide. The spend counter is deliberately not advanced yet either — the note is committed to be paid but has not been paid, so required_vault_lamports stays an upper bound on what is owed until settlement disburses. The relay signs, never the member: a member paying their own fee would sign with their own wallet and destroy their own anonymity. Relaying is permissionless. The k floor bounds program-visible membership only. The comment says so, because the effective anonymity set is smaller once an observer partitions members by funding provenance, and that is measured off-chain rather than asserted away here. Also adds the reproducible "mirror setup" and "mirror verify-setup" commands and the generated verifying key (3 public inputs, sha256 b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7). verify-setup binds the whole key — alpha, beta, gamma, delta and every IC point — so a transcript carrying a key for a different circuit cannot pass; a competing ceremony verifier checks delta alone and would certify one. --- Cargo.lock | 138 ++++++++++++++++ Cargo.toml | 3 + crates/mirror-cli/Cargo.toml | 3 + crates/mirror-cli/src/main.rs | 100 +++++++++++- programs/mirror-pool/src/error.rs | 14 ++ programs/mirror-pool/src/instruction.rs | 101 +++++++++++- programs/mirror-pool/src/lib.rs | 2 + programs/mirror-pool/src/pda.rs | 33 ++++ programs/mirror-pool/src/processor.rs | 174 +++++++++++++++++++- programs/mirror-pool/src/spend.rs | 203 ++++++++++++++++++++++++ programs/mirror-pool/src/vk.rs | 78 +++++++++ 11 files changed, 842 insertions(+), 7 deletions(-) create mode 100644 programs/mirror-pool/src/spend.rs create mode 100644 programs/mirror-pool/src/vk.rs diff --git a/Cargo.lock b/Cargo.lock index c26f074a..413f2096 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,56 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + [[package]] name = "anyhow" version = "1.0.104" @@ -538,6 +588,52 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "clap" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91e0c145792ef73a6ad36d27c75ac09f1832222a3c209689d90f534685ee5b7" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f09628afdcc538b57f3c6341e9c8e9970f18e4a481690a64974d7023bd33548b" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d012d2b9d65aca7f18f4d9878a045bc17899bba951561ba5ec3c2ba1eed9a061" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + [[package]] name = "const-oid" version = "0.9.6" @@ -901,6 +997,18 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hmac" version = "0.12.1" @@ -926,6 +1034,12 @@ dependencies = [ "hashbrown 0.17.1", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.10.5" @@ -1077,11 +1191,14 @@ name = "mirror-cli" version = "0.1.0" dependencies = [ "anyhow", + "clap", + "hex", "mirror-circuit", "mirror-core", "mirror-provenance", "serde", "serde_json", + "sha2", ] [[package]] @@ -1146,6 +1263,12 @@ version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "parking_lot" version = "0.12.5" @@ -2237,6 +2360,12 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.5" @@ -2280,6 +2409,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "winnow" version = "1.0.4" diff --git a/Cargo.toml b/Cargo.toml index 0d9026a6..1b5d80da 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,9 @@ thiserror = "2" anyhow = "1" serde = { version = "1", features = ["derive"] } serde_json = "1" +clap = { version = "4", features = ["derive"] } +sha2 = "0.10" +hex = "0.4" mirror-core = { path = "crates/mirror-core" } mirror-circuit = { path = "crates/mirror-circuit" } diff --git a/crates/mirror-cli/Cargo.toml b/crates/mirror-cli/Cargo.toml index 5db9157d..866543ff 100644 --- a/crates/mirror-cli/Cargo.toml +++ b/crates/mirror-cli/Cargo.toml @@ -13,5 +13,8 @@ mirror-core.workspace = true mirror-circuit.workspace = true mirror-provenance.workspace = true anyhow.workspace = true +clap.workspace = true +sha2.workspace = true +hex.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 9f446aea..d46eb6cc 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -1,4 +1,98 @@ -fn main() -> anyhow::Result<()> { - println!("mirror-pool CLI"); - Ok(()) +//! `mirror` — the operator and member CLI. + +use anyhow::{Context, Result}; +use clap::{Parser, Subcommand}; +use sha2::{Digest, Sha256}; +use std::path::PathBuf; + +/// The seed the committed verifying key was generated from. +/// +/// Public on purpose. It makes the setup reproducible — anyone can re-derive the +/// deployed key and check it against the circuit in this repository — and it +/// makes the setup insecure in exactly the way a public seed implies: the toxic +/// waste is public, so proofs are forgeable. That trade is stated rather than +/// hidden, and the production path is a multi-party ceremony. +/// +/// A competing submission publishes its entropy string *and* gitignores the +/// proving key, so its setup is insecure and unreproducible at the same time — +/// no third party can produce a valid proof for its deployed program at all. +pub const DEV_SETUP_SEED: &str = "mirror-pool-reproducible-dev-setup-v1"; + +#[derive(Parser)] +#[command( + name = "mirror", + about = "mirror-pool: a behavioral anonymity set on Solana", + version +)] +struct Cli { + #[command(subcommand)] + command: Command, +} + +#[derive(Subcommand)] +enum Command { + /// Generates the Groth16 keys and writes the verifying key as Rust source + /// for the on-chain program. + Setup { + #[arg(long, default_value = DEV_SETUP_SEED)] + seed: String, + #[arg(long, default_value = "programs/mirror-pool/src/vk.rs")] + out: PathBuf, + }, + /// Recomputes the verifying key from a seed and reports its digest. + /// + /// This is the check a third party runs. It binds the *whole* key — alpha, + /// beta, gamma, delta and every IC point — not just delta, so a transcript + /// carrying a key belonging to a different circuit cannot pass. A competing + /// submission's ceremony verifier checks delta alone and would certify one. + VerifySetup { + #[arg(long, default_value = DEV_SETUP_SEED)] + seed: String, + /// Expected digest, as printed by `setup`. + #[arg(long)] + expect: Option, + }, +} + +fn vk_digest(vk: &mirror_circuit::SolanaVerifyingKey) -> String { + let mut hasher = Sha256::new(); + hasher.update(vk.digest_preimage()); + hex::encode(hasher.finalize()) +} + +fn main() -> Result<()> { + match Cli::parse().command { + Command::Setup { seed, out } => { + eprintln!("generating keys from seed {seed:?} (this takes a moment)"); + let keys = mirror_circuit::generate_reproducible(seed.as_bytes()) + .map_err(|e| anyhow::anyhow!("{e}"))?; + let svk = keys.solana_vk(); + let source = mirror_circuit::vk_to_rust_source(&svk); + if let Some(parent) = out.parent() { + std::fs::create_dir_all(parent).ok(); + } + std::fs::write(&out, source).with_context(|| format!("writing {}", out.display()))?; + println!("wrote {}", out.display()); + println!("public inputs: {}", svk.public_input_count()); + println!("vk sha256: {}", vk_digest(&svk)); + Ok(()) + } + Command::VerifySetup { seed, expect } => { + let keys = mirror_circuit::generate_reproducible(seed.as_bytes()) + .map_err(|e| anyhow::anyhow!("{e}"))?; + let digest = vk_digest(&keys.solana_vk()); + println!("vk sha256: {digest}"); + match expect { + Some(want) if want.eq_ignore_ascii_case(&digest) => { + println!("MATCH — this seed and this circuit produce that key"); + Ok(()) + } + Some(want) => { + eprintln!("MISMATCH — expected {want}"); + std::process::exit(1); + } + None => Ok(()), + } + } + } } diff --git a/programs/mirror-pool/src/error.rs b/programs/mirror-pool/src/error.rs index 84b471d7..bacf1053 100644 --- a/programs/mirror-pool/src/error.rs +++ b/programs/mirror-pool/src/error.rs @@ -40,6 +40,20 @@ pub enum MirrorProgramError { /// because the two competing implementations are both drainable at exactly /// this point. InsolventVault = 13, + /// The spend account has the wrong length or an unrecognised version. + InvalidSpendAccount = 14, + /// This nullifier has already been recorded: the note is spent. + NullifierAlreadySpent = 15, + /// The spend has already been executed. + AlreadySettled = 16, + /// The proof references a root the pool does not retain. + UnknownRoot = 17, + /// The Groth16 proof did not verify. + ProofVerificationFailed = 18, + /// The pool holds fewer notes than its anonymity floor requires. + BelowAnonymityFloor = 19, + /// The relay fee is not less than the denomination. + RelayFeeTooLarge = 20, } impl From for ProgramError { diff --git a/programs/mirror-pool/src/instruction.rs b/programs/mirror-pool/src/instruction.rs index 1ac1f0e1..f46e525a 100644 --- a/programs/mirror-pool/src/instruction.rs +++ b/programs/mirror-pool/src/instruction.rs @@ -18,6 +18,7 @@ use crate::error::MirrorProgramError; pub enum Tag { InitPool = 0, Deposit = 1, + SubmitSpend = 2, } impl Tag { @@ -25,11 +26,18 @@ impl Tag { match v { 0 => Ok(Tag::InitPool), 1 => Ok(Tag::Deposit), + 2 => Ok(Tag::SubmitSpend), _ => Err(MirrorProgramError::MalformedInstruction), } } } +/// `SubmitSpend` carries a 256-byte proof, so it is far larger than the other +/// variants. Boxing it — clippy's usual remedy — would move it to the heap, and +/// a heap allocation inside an on-chain program costs compute units to avoid a +/// stack cost we can comfortably afford: 368 bytes against a 4 KB frame, decoded +/// once per transaction. The size difference is deliberate. +#[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Instruction { /// Creates the pool and its vault, and seeds the accumulator to an empty @@ -45,12 +53,32 @@ pub enum Instruction { /// The amount is not a parameter. It is read from the pool, so a deposit /// cannot claim a size the pool did not set. Deposit { commitment: [u8; 32] }, + /// Proves membership, burns the nullifier, and records the authorised + /// action. Nothing is paid out here — settlement executes the batch. + /// + /// The action binding is **not** transmitted. It is recomputed on-chain from + /// `selector`, `beneficiary` and `relay_fee` and used as the third public + /// input, so a relay that alters any of them produces a different binding + /// and the pairing simply fails. There is no separate field to forget to + /// check. + SubmitSpend { + proof_a: [u8; 64], + proof_b: [u8; 128], + proof_c: [u8; 64], + root: [u8; 32], + nullifier: [u8; 32], + selector: u64, + beneficiary: [u8; 32], + relay_fee: u64, + }, } /// `InitPool`: tag + u64 + u64 + u32. pub const INIT_POOL_LEN: usize = 1 + 8 + 8 + 4; /// `Deposit`: tag + one field element. pub const DEPOSIT_LEN: usize = 1 + 32; +/// `SubmitSpend`: tag + proof + root + nullifier + selector + beneficiary + fee. +pub const SUBMIT_SPEND_LEN: usize = 1 + 64 + 128 + 64 + 32 + 32 + 8 + 32 + 8; fn read_u64(data: &[u8], at: usize) -> u64 { let mut b = [0u8; 8]; @@ -91,6 +119,35 @@ impl Instruction { commitment.copy_from_slice(&data[1..33]); Ok(Instruction::Deposit { commitment }) } + Tag::SubmitSpend => { + if data.len() != SUBMIT_SPEND_LEN { + return Err(MirrorProgramError::MalformedInstruction); + } + let mut proof_a = [0u8; 64]; + let mut proof_b = [0u8; 128]; + let mut proof_c = [0u8; 64]; + let mut root = [0u8; 32]; + let mut nullifier = [0u8; 32]; + let mut beneficiary = [0u8; 32]; + proof_a.copy_from_slice(&data[1..65]); + proof_b.copy_from_slice(&data[65..193]); + proof_c.copy_from_slice(&data[193..257]); + root.copy_from_slice(&data[257..289]); + nullifier.copy_from_slice(&data[289..321]); + let selector = read_u64(data, 321); + beneficiary.copy_from_slice(&data[329..361]); + let relay_fee = read_u64(data, 361); + Ok(Instruction::SubmitSpend { + proof_a, + proof_b, + proof_c, + root, + nullifier, + selector, + beneficiary, + relay_fee, + }) + } } } @@ -115,6 +172,28 @@ impl Instruction { out.extend_from_slice(commitment); out } + Instruction::SubmitSpend { + proof_a, + proof_b, + proof_c, + root, + nullifier, + selector, + beneficiary, + relay_fee, + } => { + let mut out = Vec::with_capacity(SUBMIT_SPEND_LEN); + out.push(Tag::SubmitSpend as u8); + out.extend_from_slice(proof_a); + out.extend_from_slice(proof_b); + out.extend_from_slice(proof_c); + out.extend_from_slice(root); + out.extend_from_slice(nullifier); + out.extend_from_slice(&selector.to_le_bytes()); + out.extend_from_slice(beneficiary); + out.extend_from_slice(&relay_fee.to_le_bytes()); + out + } } } } @@ -137,9 +216,23 @@ mod tests { } } + fn submit_spend() -> Instruction { + // Distinct byte patterns per field so a swapped offset cannot pass. + Instruction::SubmitSpend { + proof_a: [0xA1; 64], + proof_b: [0xB2; 128], + proof_c: [0xC3; 64], + root: [0xD4; 32], + nullifier: [0xE5; 32], + selector: 0x0102_0304_0506_0708, + beneficiary: [0xF6; 32], + relay_fee: 0x1112_1314_1516_1718, + } + } + #[test] fn packing_then_unpacking_is_the_identity() { - for ix in [init(), deposit()] { + for ix in [init(), deposit(), submit_spend()] { assert_eq!(Instruction::unpack(&ix.pack()).unwrap(), ix); } } @@ -149,8 +242,10 @@ mod tests { // These are the on-chain ABI. A change here breaks every deployed client. assert_eq!(init().pack().len(), INIT_POOL_LEN); assert_eq!(deposit().pack().len(), DEPOSIT_LEN); + assert_eq!(submit_spend().pack().len(), SUBMIT_SPEND_LEN); assert_eq!(INIT_POOL_LEN, 21); assert_eq!(DEPOSIT_LEN, 33); + assert_eq!(SUBMIT_SPEND_LEN, 369); } #[test] @@ -163,7 +258,7 @@ mod tests { #[test] fn an_unknown_tag_is_refused() { - for tag in [2u8, 3, 99, 255] { + for tag in [3u8, 4, 99, 255] { assert!( matches!( Instruction::unpack(&[tag]), @@ -178,7 +273,7 @@ mod tests { /// buffer must not be zero-extended and a long one must not be truncated. #[test] fn every_wrong_length_is_refused() { - for ix in [init(), deposit()] { + for ix in [init(), deposit(), submit_spend()] { let good = ix.pack(); for len in 0..good.len() { assert!( diff --git a/programs/mirror-pool/src/lib.rs b/programs/mirror-pool/src/lib.rs index 7547df30..e4815bb4 100644 --- a/programs/mirror-pool/src/lib.rs +++ b/programs/mirror-pool/src/lib.rs @@ -8,7 +8,9 @@ pub mod error; pub mod instruction; pub mod pda; pub mod processor; +pub mod spend; pub mod state; +pub mod vk; pub use error::MirrorProgramError; pub use instruction::Instruction; diff --git a/programs/mirror-pool/src/pda.rs b/programs/mirror-pool/src/pda.rs index 443eef45..7339e9b1 100644 --- a/programs/mirror-pool/src/pda.rs +++ b/programs/mirror-pool/src/pda.rs @@ -10,6 +10,7 @@ use solana_program::pubkey::Pubkey; pub const POOL_SEED: &[u8] = b"pool"; pub const VAULT_SEED: &[u8] = b"vault"; +pub const SPEND_SEED: &[u8] = b"spend"; /// The pool account for a denomination. pub fn pool_address(program_id: &Pubkey, denomination: u64) -> (Pubkey, u8) { @@ -26,6 +27,15 @@ pub fn vault_address(program_id: &Pubkey, pool: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&[VAULT_SEED, pool.as_ref()], program_id) } +/// The spend record for a nullifier. +/// +/// Seeded by the nullifier itself, so the account's existence *is* the replay +/// guard: a second spend of the same note fails at account creation, before the +/// verifier is even reached. +pub fn spend_address(program_id: &Pubkey, pool: &Pubkey, nullifier: &[u8; 32]) -> (Pubkey, u8) { + Pubkey::find_program_address(&[SPEND_SEED, pool.as_ref(), nullifier], program_id) +} + #[cfg(test)] mod tests { use super::*; @@ -57,6 +67,29 @@ mod tests { assert_eq!(vault, vault_address(&program, &pool).0); } + #[test] + fn each_nullifier_gets_its_own_spend_record() { + let program = Pubkey::new_unique(); + let (pool, _) = pool_address(&program, 1); + let a = spend_address(&program, &pool, &[1u8; 32]).0; + let b = spend_address(&program, &pool, &[2u8; 32]).0; + assert_ne!(a, b); + assert_eq!(a, spend_address(&program, &pool, &[1u8; 32]).0); + } + + #[test] + fn one_nullifier_in_two_pools_is_two_records() { + // Pool-scoped, so a nullifier burned in one denomination does not block + // an unrelated note in another. + let program = Pubkey::new_unique(); + let (pool_a, _) = pool_address(&program, 1); + let (pool_b, _) = pool_address(&program, 2); + assert_ne!( + spend_address(&program, &pool_a, &[7u8; 32]).0, + spend_address(&program, &pool_b, &[7u8; 32]).0 + ); + } + #[test] fn a_different_program_yields_different_addresses() { let (a, _) = pool_address(&Pubkey::new_unique(), 1); diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 4869b281..f807ecfa 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -3,7 +3,8 @@ use crate::{ error::MirrorProgramError, instruction::Instruction, - pda::{pool_address, vault_address, POOL_SEED, VAULT_SEED}, + pda::{pool_address, spend_address, vault_address, POOL_SEED, SPEND_SEED, VAULT_SEED}, + spend::{Spend, SPEND_LEN}, state::{Pool, POOL_LEN}, }; use mirror_core::{hash_node, Field, TREE_DEPTH}; @@ -25,9 +26,44 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> Pr k_floor, } => init_pool(program_id, accounts, denomination, entry_fee, k_floor), Instruction::Deposit { commitment } => deposit(program_id, accounts, commitment), + Instruction::SubmitSpend { + proof_a, + proof_b, + proof_c, + root, + nullifier, + selector, + beneficiary, + relay_fee, + } => submit_spend( + program_id, + accounts, + SpendRequest { + proof_a, + proof_b, + proof_c, + root, + nullifier, + selector, + beneficiary, + relay_fee, + }, + ), } } +/// The fields of a spend, grouped so the handler takes one argument. +struct SpendRequest { + proof_a: [u8; 64], + proof_b: [u8; 128], + proof_c: [u8; 64], + root: [u8; 32], + nullifier: [u8; 32], + selector: u64, + beneficiary: [u8; 32], + relay_fee: u64, +} + /// Creates the pool and its vault and seeds the accumulator to an empty tree. /// /// Permissionless: the first caller for a denomination creates the crowd @@ -175,6 +211,142 @@ fn deposit(program_id: &Pubkey, accounts: &[AccountInfo], commitment: [u8; 32]) Ok(()) } +/// Proves membership, burns the nullifier, and records the authorised action. +/// +/// Nothing is paid out here. Settlement executes the batch, so every action in +/// an epoch lands on one timestamp and in one ordering — which is the point of a +/// synchronised crowd, and the reason a per-spend payout would leak exactly what +/// the pool exists to hide. +/// +/// The relay signs, not the member. A member who pays their own fee signs with +/// their own wallet and destroys their own anonymity, so no member key appears +/// on chain at any point in this path. Relaying is permissionless: any key may +/// do it, and there is no authority whose absence freezes the pool. +/// +/// Note the ordering. The proof is verified *before* the spend account is +/// created, but the account's existence is what makes replay impossible, and +/// account creation fails if it already exists. So a replayed proof — however +/// valid — cannot produce a second record. +fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest) -> ProgramResult { + let iter = &mut accounts.iter(); + let relay = next_account_info(iter)?; + let pool_account = next_account_info(iter)?; + let spend_account = next_account_info(iter)?; + let system = next_account_info(iter)?; + + if !relay.is_signer { + return Err(MirrorProgramError::MissingSignature.into()); + } + if !system_program::check_id(system.key) { + return Err(MirrorProgramError::InvalidOwner.into()); + } + if pool_account.owner != program_id { + return Err(MirrorProgramError::InvalidOwner.into()); + } + + // Canonical scalars, checked before anything else touches them. The Groth16 + // verifier rejects public inputs at or above the modulus, and the Poseidon + // syscall rejects non-canonical preimages; doing it here turns both into one + // named error instead of an opaque failure deeper in. + let root = Field::from_bytes(req.root).map_err(MirrorProgramError::from)?; + let nullifier_field = Field::from_bytes(req.nullifier).map_err(MirrorProgramError::from)?; + + let (denomination, k_floor, deposit_count) = { + let mut data = pool_account.try_borrow_mut_data()?; + let pool = Pool::load(&mut data)?; + (pool.denomination(), pool.k_floor(), pool.deposit_count()) + }; + + // The relay is paid out of the denomination, so a fee at or above it would + // leave the member nothing and, at exactly the denomination, would let a + // relay take the whole note. + if req.relay_fee >= denomination { + return Err(MirrorProgramError::RelayFeeTooLarge.into()); + } + + // The anonymity floor. This bounds *program-visible* membership: how many + // notes the tree holds. It is not the effective anonymity set, which is + // smaller because an observer can partition members by funding provenance — + // that is measured off-chain and reported honestly rather than asserted away + // here. + if deposit_count < k_floor as u64 { + return Err(MirrorProgramError::BelowAnonymityFloor.into()); + } + + { + let mut data = pool_account.try_borrow_mut_data()?; + let pool = Pool::load(&mut data)?; + if !pool.knows_root(root) { + return Err(MirrorProgramError::UnknownRoot.into()); + } + } + + // Recomputed, never transmitted. If the relay altered the selector, the + // beneficiary or its own fee, this binding differs from the one the prover + // committed to and the pairing fails. There is no separate field that could + // be checked incorrectly or forgotten. + let binding = mirror_core::action_binding(req.selector, &req.beneficiary, req.relay_fee) + .map_err(MirrorProgramError::from)?; + + let public_inputs: [[u8; 32]; 3] = [ + root.to_bytes(), + nullifier_field.to_bytes(), + binding.to_bytes(), + ]; + let mut verifier = groth16_solana::groth16::Groth16Verifier::<3>::new( + &req.proof_a, + &req.proof_b, + &req.proof_c, + &public_inputs, + &crate::vk::VERIFYING_KEY, + ) + .map_err(|_| MirrorProgramError::ProofVerificationFailed)?; + verifier + .verify() + .map_err(|_| MirrorProgramError::ProofVerificationFailed)?; + + let (expected_spend, spend_bump) = spend_address(program_id, pool_account.key, &req.nullifier); + if *spend_account.key != expected_spend { + return Err(MirrorProgramError::InvalidPda.into()); + } + + // Creation fails if the account already exists, which is the replay guard. + let rent = Rent::get()?; + invoke_signed( + &system_instruction::create_account( + relay.key, + spend_account.key, + rent.minimum_balance(SPEND_LEN), + SPEND_LEN as u64, + program_id, + ), + &[relay.clone(), spend_account.clone(), system.clone()], + &[&[ + SPEND_SEED, + pool_account.key.as_ref(), + &req.nullifier, + &[spend_bump], + ]], + ) + .map_err(|_| MirrorProgramError::NullifierAlreadySpent)?; + + let mut spend_data = spend_account.try_borrow_mut_data()?; + let mut record = Spend::load_uninitialised(&mut spend_data)?; + record.initialise( + spend_bump, + req.selector, + req.relay_fee, + &req.beneficiary, + &relay.key.to_bytes(), + ); + + // The spend counter is deliberately *not* advanced here. The note is + // committed to be paid but has not been paid, so the vault must still cover + // it; leaving the counter alone keeps `required_vault_lamports` an upper + // bound on what is owed until settlement actually disburses. + Ok(()) +} + /// Appends `leaf` to the pool's frontier, returning its index. /// /// The same walk as `mirror_core::Frontier::insert`, over account bytes. The diff --git a/programs/mirror-pool/src/spend.rs b/programs/mirror-pool/src/spend.rs new file mode 100644 index 00000000..70756847 --- /dev/null +++ b/programs/mirror-pool/src/spend.rs @@ -0,0 +1,203 @@ +//! The spend record. +//! +//! One account per nullifier, seeded by it. Its *existence* is the replay +//! guard — creating it fails if it already exists, so a second spend of the same +//! note cannot even reach the verifier — and its *contents* are the action the +//! proof authorised, held until settlement. +//! +//! Folding the nullifier marker and the pending action into one account is +//! deliberate: two accounts keyed by the same value can disagree, and an account +//! that exists to be checked but never read is easy to forget to check. + +use crate::error::MirrorProgramError; + +/// Not yet executed. +pub const STATUS_PENDING: u8 = 1; +/// Executed at settlement. +pub const STATUS_SETTLED: u8 = 2; + +mod offset { + pub const VERSION: usize = 0; + pub const STATUS: usize = 1; + pub const BUMP: usize = 2; + pub const _RESERVED: usize = 3; + pub const SELECTOR: usize = 8; + pub const RELAY_FEE: usize = 16; + pub const BENEFICIARY: usize = 24; + pub const RELAY: usize = 56; + pub const END: usize = 88; +} + +pub const SPEND_LEN: usize = offset::END; +pub const SPEND_VERSION: u8 = 1; + +const _: () = assert!(SPEND_LEN == 88); + +pub struct Spend<'a> { + data: &'a mut [u8], +} + +impl<'a> Spend<'a> { + pub fn load(data: &'a mut [u8]) -> Result { + if data.len() != SPEND_LEN || data[offset::VERSION] != SPEND_VERSION { + return Err(MirrorProgramError::InvalidSpendAccount); + } + Ok(Spend { data }) + } + + pub fn load_uninitialised(data: &'a mut [u8]) -> Result { + if data.len() != SPEND_LEN { + return Err(MirrorProgramError::InvalidSpendAccount); + } + // A non-zero version means this nullifier has already been recorded. + // Account creation would normally have failed first; this is the second + // line of the replay guard, not the first. + if data[offset::VERSION] != 0 { + return Err(MirrorProgramError::NullifierAlreadySpent); + } + Ok(Spend { data }) + } + + #[allow(clippy::too_many_arguments)] + pub fn initialise( + &mut self, + bump: u8, + selector: u64, + relay_fee: u64, + beneficiary: &[u8; 32], + relay: &[u8; 32], + ) { + self.data[offset::VERSION] = SPEND_VERSION; + self.data[offset::STATUS] = STATUS_PENDING; + self.data[offset::BUMP] = bump; + self.data[offset::SELECTOR..offset::SELECTOR + 8].copy_from_slice(&selector.to_le_bytes()); + self.data[offset::RELAY_FEE..offset::RELAY_FEE + 8] + .copy_from_slice(&relay_fee.to_le_bytes()); + self.data[offset::BENEFICIARY..offset::BENEFICIARY + 32].copy_from_slice(beneficiary); + self.data[offset::RELAY..offset::RELAY + 32].copy_from_slice(relay); + } + + pub fn status(&self) -> u8 { + self.data[offset::STATUS] + } + pub fn bump(&self) -> u8 { + self.data[offset::BUMP] + } + pub fn selector(&self) -> u64 { + let mut b = [0u8; 8]; + b.copy_from_slice(&self.data[offset::SELECTOR..offset::SELECTOR + 8]); + u64::from_le_bytes(b) + } + pub fn relay_fee(&self) -> u64 { + let mut b = [0u8; 8]; + b.copy_from_slice(&self.data[offset::RELAY_FEE..offset::RELAY_FEE + 8]); + u64::from_le_bytes(b) + } + pub fn beneficiary(&self) -> [u8; 32] { + let mut b = [0u8; 32]; + b.copy_from_slice(&self.data[offset::BENEFICIARY..offset::BENEFICIARY + 32]); + b + } + pub fn relay(&self) -> [u8; 32] { + let mut b = [0u8; 32]; + b.copy_from_slice(&self.data[offset::RELAY..offset::RELAY + 32]); + b + } + + /// Marks the spend executed, refusing to do so twice. + pub fn mark_settled(&mut self) -> Result<(), MirrorProgramError> { + if self.status() != STATUS_PENDING { + return Err(MirrorProgramError::AlreadySettled); + } + self.data[offset::STATUS] = STATUS_SETTLED; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn fresh() -> Vec { + vec![0u8; SPEND_LEN] + } + + #[test] + fn a_spend_reads_back_what_was_written() { + let mut data = fresh(); + let beneficiary = [9u8; 32]; + let relay = [4u8; 32]; + { + let mut s = Spend::load_uninitialised(&mut data).unwrap(); + s.initialise(251, 7, 12_345, &beneficiary, &relay); + } + let s = Spend::load(&mut data).unwrap(); + assert_eq!(s.status(), STATUS_PENDING); + assert_eq!(s.bump(), 251); + assert_eq!(s.selector(), 7); + assert_eq!(s.relay_fee(), 12_345); + assert_eq!(s.beneficiary(), beneficiary); + assert_eq!(s.relay(), relay); + } + + #[test] + fn an_existing_record_refuses_reinitialisation() { + let mut data = fresh(); + { + let mut s = Spend::load_uninitialised(&mut data).unwrap(); + s.initialise(1, 1, 1, &[1u8; 32], &[2u8; 32]); + } + assert!(matches!( + Spend::load_uninitialised(&mut data), + Err(MirrorProgramError::NullifierAlreadySpent) + )); + } + + #[test] + fn a_spend_settles_exactly_once() { + let mut data = fresh(); + { + let mut s = Spend::load_uninitialised(&mut data).unwrap(); + s.initialise(1, 1, 1, &[1u8; 32], &[2u8; 32]); + } + let mut s = Spend::load(&mut data).unwrap(); + s.mark_settled().unwrap(); + assert_eq!(s.status(), STATUS_SETTLED); + assert!(matches!( + s.mark_settled(), + Err(MirrorProgramError::AlreadySettled) + )); + } + + #[test] + fn a_wrong_length_account_is_refused() { + let mut short = vec![0u8; SPEND_LEN - 1]; + assert!(Spend::load(&mut short).is_err()); + assert!(Spend::load_uninitialised(&mut short).is_err()); + } + + #[test] + fn fields_do_not_overlap() { + // Distinct values in every field; if two overlapped, one would clobber + // the other and this would fail. + let mut data = fresh(); + let beneficiary = [0xAB; 32]; + let relay = [0xCD; 32]; + { + let mut s = Spend::load_uninitialised(&mut data).unwrap(); + s.initialise( + 0xEE, + 0x1122_3344_5566_7788, + 0x99AA_BBCC_DDEE_FF00, + &beneficiary, + &relay, + ); + } + let s = Spend::load(&mut data).unwrap(); + assert_eq!(s.bump(), 0xEE); + assert_eq!(s.selector(), 0x1122_3344_5566_7788); + assert_eq!(s.relay_fee(), 0x99AA_BBCC_DDEE_FF00); + assert_eq!(s.beneficiary(), beneficiary); + assert_eq!(s.relay(), relay); + } +} diff --git a/programs/mirror-pool/src/vk.rs b/programs/mirror-pool/src/vk.rs new file mode 100644 index 00000000..391d7b26 --- /dev/null +++ b/programs/mirror-pool/src/vk.rs @@ -0,0 +1,78 @@ +//! Generated by `mirror setup`. Do not edit. +//! +//! Verifies 3 public inputs: root, nullifier, action_binding. + +use groth16_solana::groth16::Groth16Verifyingkey; + +pub const VK_ALPHA_G1: [u8; 64] = [ + 21, 82, 151, 215, 133, 209, 133, 55, 176, 54, 67, 95, 195, 62, 157, 215, 31, 7, 105, 216, 244, + 250, 149, 193, 71, 181, 36, 43, 228, 229, 195, 13, 21, 9, 158, 45, 229, 19, 240, 147, 210, 141, + 128, 164, 248, 125, 69, 35, 90, 111, 251, 90, 119, 54, 197, 89, 7, 143, 146, 56, 97, 14, 139, + 7, +]; + +pub const VK_BETA_G2: [u8; 128] = [ + 9, 82, 221, 5, 233, 247, 2, 96, 83, 253, 67, 2, 10, 138, 173, 130, 70, 142, 123, 171, 205, 241, + 190, 121, 108, 236, 13, 236, 114, 47, 161, 87, 39, 45, 103, 187, 196, 122, 67, 169, 78, 44, + 144, 72, 14, 131, 168, 237, 35, 124, 214, 42, 60, 168, 187, 13, 246, 188, 146, 214, 246, 123, + 218, 55, 10, 196, 101, 47, 197, 232, 124, 214, 127, 68, 155, 25, 28, 49, 136, 220, 218, 223, + 252, 132, 8, 133, 79, 203, 16, 6, 230, 6, 166, 30, 66, 71, 36, 47, 248, 181, 201, 98, 233, 199, + 176, 245, 112, 192, 147, 101, 159, 91, 202, 110, 10, 185, 241, 90, 68, 47, 247, 200, 37, 106, + 253, 129, 167, 147, +]; + +pub const VK_GAMMA_G2: [u8; 128] = [ + 35, 75, 106, 178, 55, 49, 73, 97, 91, 224, 163, 227, 199, 118, 23, 111, 151, 210, 181, 65, 227, + 60, 114, 66, 8, 157, 245, 33, 107, 30, 38, 64, 32, 161, 157, 224, 170, 224, 15, 121, 239, 203, + 177, 82, 175, 166, 6, 236, 178, 169, 161, 16, 159, 115, 122, 235, 37, 226, 113, 43, 181, 164, + 54, 0, 33, 207, 158, 226, 182, 57, 223, 100, 252, 249, 45, 246, 200, 14, 108, 229, 219, 29, + 240, 134, 235, 80, 148, 207, 107, 80, 234, 241, 121, 163, 7, 84, 4, 109, 157, 126, 104, 68, 96, + 189, 78, 189, 208, 76, 46, 20, 233, 68, 162, 127, 16, 58, 251, 140, 15, 90, 2, 34, 21, 14, 32, + 169, 251, 211, +]; + +pub const VK_DELTA_G2: [u8; 128] = [ + 40, 20, 18, 97, 68, 157, 250, 8, 221, 119, 154, 73, 222, 28, 100, 106, 253, 91, 198, 171, 86, + 72, 160, 111, 38, 32, 187, 208, 130, 25, 223, 122, 36, 59, 96, 41, 163, 186, 118, 220, 146, + 105, 183, 85, 90, 72, 94, 14, 104, 189, 238, 53, 195, 49, 5, 231, 89, 213, 229, 203, 101, 54, + 50, 36, 5, 175, 247, 82, 89, 10, 182, 243, 105, 5, 71, 34, 143, 218, 179, 1, 245, 101, 147, 8, + 208, 213, 161, 118, 84, 17, 184, 242, 92, 184, 143, 63, 7, 4, 188, 135, 100, 220, 94, 140, 210, + 151, 158, 172, 69, 225, 61, 55, 105, 28, 142, 150, 15, 189, 49, 16, 42, 204, 189, 160, 87, 127, + 3, 201, +]; + +pub const VK_IC: [[u8; 64]; 4] = [ + [ + 20, 219, 151, 102, 164, 174, 178, 55, 2, 228, 221, 187, 172, 199, 119, 6, 177, 227, 172, + 212, 219, 233, 72, 136, 13, 45, 165, 160, 150, 67, 122, 225, 14, 157, 212, 232, 42, 17, 86, + 110, 221, 71, 23, 92, 193, 113, 72, 153, 111, 57, 140, 234, 65, 62, 248, 8, 146, 125, 169, + 106, 66, 172, 201, 244, + ], + [ + 17, 241, 112, 76, 90, 220, 39, 228, 16, 33, 184, 185, 105, 35, 155, 123, 64, 105, 83, 149, + 108, 219, 185, 187, 76, 123, 90, 255, 7, 65, 88, 6, 41, 238, 235, 201, 21, 15, 93, 181, + 195, 85, 111, 200, 6, 180, 177, 182, 31, 99, 47, 17, 253, 15, 2, 69, 108, 5, 232, 237, 1, + 161, 0, 198, + ], + [ + 13, 120, 152, 134, 145, 252, 68, 46, 75, 136, 106, 72, 177, 67, 152, 46, 47, 68, 177, 210, + 98, 124, 230, 61, 238, 66, 104, 236, 191, 70, 46, 218, 26, 46, 194, 255, 215, 146, 70, 137, + 183, 164, 104, 10, 60, 100, 190, 150, 16, 223, 113, 213, 73, 121, 245, 61, 200, 123, 183, + 120, 158, 245, 88, 44, + ], + [ + 34, 22, 145, 135, 219, 16, 59, 168, 253, 71, 187, 213, 93, 117, 79, 0, 211, 18, 38, 69, 58, + 50, 106, 125, 41, 23, 119, 36, 26, 142, 79, 249, 18, 170, 34, 68, 137, 114, 130, 7, 23, 50, + 131, 197, 43, 227, 170, 73, 126, 44, 12, 252, 195, 181, 113, 202, 228, 32, 144, 164, 122, + 227, 146, 198, + ], +]; + +pub const VERIFYING_KEY: Groth16Verifyingkey<'static> = Groth16Verifyingkey { + nr_pubinputs: 3, + vk_alpha_g1: VK_ALPHA_G1, + vk_beta_g2: VK_BETA_G2, + vk_gamme_g2: VK_GAMMA_G2, + vk_delta_g2: VK_DELTA_G2, + vk_ic: &VK_IC, +}; From ff0cf51a3351feda0d3da798ed6c3e5456f2700e Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 04:57:24 -0300 Subject: [PATCH 12/83] test(program): end-to-end suite against the real compiled program MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Loads the .so that make build-sbf produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall. Every other test in this repository calls functions directly; this is the only one that can catch a divergence between what the host believes and what the chain does. submit_spend costs 111,029 compute units, inside the 200k default budget with room to spare. Each rejection carries its own code, and the cheap checks run first: below the anonymity floor fails at 1,876 CU and an unknown root at 3,099, both before the pairing is ever attempted. Two real defects this surfaced: - The replay test passed for the wrong reason. Resubmitting through the same relay produced a byte-identical transaction, so the runtime rejected it as AlreadyProcessed and the nullifier guard was never reached. The replay now goes through a different relay, and the test asserts the failure is *not* AlreadyProcessed so it cannot silently regress to that. - With the guard genuinely reached, the error was Custom(0) — the system program's "already in use" — because a failed CPI terminates the instruction before the caller's map_err runs. The guard is now an explicit check ahead of account creation, so a replay reports NullifierAlreadySpent. That matters because devnet evidence records error codes as proof of negative cases, and a bare system-program error proves nothing about this program. Getting here also required aligning the dependency graph: the Solana crates are mid-migration on wincode, and solana-address 2.7 implements against 0.6 while solana-instruction 3.4, the newest published, still derives against 0.5. Eight crates are pinned to the 0.5 line so exactly one version exists in the lockfile. --- Cargo.lock | 1893 ++++++++++++++++++++-- Cargo.toml | 7 + programs/mirror-pool/Cargo.toml | 10 + programs/mirror-pool/src/processor.rs | 13 +- programs/mirror-pool/tests/end_to_end.rs | 470 ++++++ 5 files changed, 2289 insertions(+), 104 deletions(-) create mode 100644 programs/mirror-pool/tests/end_to_end.rs diff --git a/Cargo.lock b/Cargo.lock index 413f2096..897cc80b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,68 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.7", + "generic-array", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm-siv" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae0784134ba9375416d469ec31e7c5f9fa94405049cf08c5ce5b4698be673e0d" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "polyval", + "subtle", + "zeroize", +] + +[[package]] +name = "agave-feature-set" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4790979fc2a3c1c494c9cb84e8b514da4b8c6a992a460a077b31b07657606f8" +dependencies = [ + "ahash", + "solana-epoch-schedule", + "solana-hash", + "solana-keypair", + "solana-pubkey 4.2.0", + "solana-sha256-hasher", + "solana-svm-feature-set", +] + +[[package]] +name = "agave-reserved-account-keys" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea33710f30b13f62e6d73dbbb114117029d29539cb9439c30ef39a0736f935cc" +dependencies = [ + "agave-feature-set", + "solana-pubkey 4.2.0", + "solana-sdk-ids", +] + [[package]] name = "ahash" version = "0.8.12" @@ -9,6 +71,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -20,6 +83,15 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anstream" version = "1.0.0" @@ -115,11 +187,11 @@ dependencies = [ "ark-std 0.5.0", "blake2", "derivative", - "digest", + "digest 0.10.7", "fnv", "hashbrown 0.14.5", "merlin", - "sha2", + "sha2 0.10.9", "tracing", ] @@ -166,7 +238,7 @@ dependencies = [ "fnv", "hashbrown 0.15.5", "itertools 0.13.0", - "num-bigint", + "num-bigint 0.4.8", "num-integer", "num-traits", "zeroize", @@ -183,9 +255,9 @@ dependencies = [ "ark-serialize 0.4.2", "ark-std 0.4.0", "derivative", - "digest", + "digest 0.10.7", "itertools 0.10.5", - "num-bigint", + "num-bigint 0.4.8", "num-traits", "paste", "rustc_version", @@ -203,10 +275,10 @@ dependencies = [ "ark-serialize 0.5.0", "ark-std 0.5.0", "arrayvec", - "digest", + "digest 0.10.7", "educe", "itertools 0.13.0", - "num-bigint", + "num-bigint 0.4.8", "num-traits", "paste", "zeroize", @@ -238,7 +310,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" dependencies = [ - "num-bigint", + "num-bigint 0.4.8", "num-traits", "proc-macro2", "quote", @@ -251,7 +323,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" dependencies = [ - "num-bigint", + "num-bigint 0.4.8", "num-traits", "proc-macro2", "quote", @@ -312,7 +384,7 @@ dependencies = [ "ark-relations", "ark-std 0.5.0", "educe", - "num-bigint", + "num-bigint 0.4.8", "num-integer", "num-traits", "tracing", @@ -337,8 +409,8 @@ checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive 0.4.2", "ark-std 0.4.0", - "digest", - "num-bigint", + "digest 0.10.7", + "num-bigint 0.4.8", ] [[package]] @@ -350,8 +422,8 @@ dependencies = [ "ark-serialize-derive 0.5.0", "ark-std 0.5.0", "arrayvec", - "digest", - "num-bigint", + "digest 0.10.7", + "num-bigint 0.4.8", ] [[package]] @@ -395,7 +467,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" dependencies = [ "num-traits", - "rand", + "rand 0.8.7", ] [[package]] @@ -405,7 +477,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" dependencies = [ "num-traits", - "rand", + "rand 0.8.7", ] [[package]] @@ -420,6 +492,12 @@ version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +[[package]] +name = "ascii" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" + [[package]] name = "autocfg" version = "1.5.1" @@ -459,13 +537,25 @@ version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" +[[package]] +name = "bitvec" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddcec3d12c579d40898fe0a9a358a803c23e9c52ca3c425707f81c9436211837" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest", + "digest 0.10.7", ] [[package]] @@ -480,6 +570,16 @@ dependencies = [ "cfg-if", "constant_time_eq", "cpufeatures 0.3.0", + "digest 0.11.3", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", ] [[package]] @@ -491,6 +591,43 @@ dependencies = [ "generic-array", ] +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "blst" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "blstrs" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8a8ed6fefbeef4a8c7b460e4110e12c5e22a5b7cf32621aae6ad650c4dcf29" +dependencies = [ + "blst", + "byte-slice-cast", + "ff", + "group", + "pairing", + "rand_core 0.6.4", + "serde", + "subtle", +] + [[package]] name = "borsh" version = "1.8.0" @@ -534,6 +671,12 @@ dependencies = [ "serde", ] +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + [[package]] name = "bytemuck" version = "1.25.2" @@ -588,6 +731,27 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.7", + "inout", +] + [[package]] name = "clap" version = "4.6.4" @@ -628,12 +792,31 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "colorchoice" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" +[[package]] +name = "combine" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" +dependencies = [ + "ascii", + "byteorder", + "either", + "memchr", + "unreachable", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -664,6 +847,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-bigint" version = "0.5.5" @@ -671,7 +860,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "subtle", "zeroize", ] @@ -683,9 +872,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -695,10 +912,11 @@ dependencies = [ "cfg-if", "cpufeatures 0.2.17", "curve25519-dalek-derive", - "digest", + "digest 0.10.7", "fiat-crypto", - "rand_core", + "rand_core 0.6.4", "rustc_version", + "serde", "subtle", "zeroize", ] @@ -758,6 +976,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "derivation-path" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5c37193a1db1d8ed868c03ec7b152175f26160a5b740e5e484143877e0adf0" + [[package]] name = "derivative" version = "2.2.0" @@ -769,18 +993,44 @@ dependencies = [ "syn 1.0.109", ] +[[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", + "block-buffer 0.10.4", "const-oid", - "crypto-common", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", + "ctutils", +] + +[[package]] +name = "eager" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe71d579d1812060163dff96056261deb5bf6729b100fa2e36a68b9649ba3d3" + [[package]] name = "ecdsa" version = "0.16.9" @@ -788,13 +1038,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest", + "digest 0.10.7", "elliptic-curve", "rfc6979", "signature", "spki", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2 0.10.9", + "subtle", + "zeroize", +] + [[package]] name = "educe" version = "0.6.0" @@ -821,17 +1096,37 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest", + "digest 0.10.7", "ff", "generic-array", "group", "pkcs8", - "rand_core", + "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] +[[package]] +name = "enum-iterator" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4549325971814bda7a44061bf3fe7e487d447cba01e4220a4b454d630d7a016" +dependencies = [ + "enum-iterator-derive", +] + +[[package]] +name = "enum-iterator-derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685adfa4d6f3d765a26bc5dbc936577de9abf756c1feeb3089b01dd395034842" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "enum-ordinalize" version = "4.4.1" @@ -870,7 +1165,8 @@ version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ - "rand_core", + "bitvec", + "rand_core 0.6.4", "subtle", ] @@ -916,6 +1212,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "generic-array" version = "0.14.7" @@ -938,6 +1240,24 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + [[package]] name = "groth16-solana" version = "0.2.0" @@ -948,8 +1268,8 @@ dependencies = [ "ark-ec 0.5.0", "ark-ff 0.5.0", "ark-serialize 0.5.0", - "num-bigint", - "solana-bn254", + "num-bigint 0.4.8", + "solana-bn254 2.2.2", "thiserror 1.0.69", ] @@ -960,10 +1280,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", - "rand_core", + "rand 0.8.7", + "rand_core 0.6.4", + "rand_xorshift", "subtle", ] +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.13.2" @@ -1003,6 +1334,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -1015,7 +1352,16 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest", + "digest 0.10.7", +] + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "typenum", ] [[package]] @@ -1034,6 +1380,15 @@ dependencies = [ "hashbrown 0.17.1", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1058,6 +1413,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.18" @@ -1074,7 +1438,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "once_cell", - "sha2", + "sha2 0.10.9", "signature", ] @@ -1099,6 +1463,52 @@ version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "libsecp256k1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79019718125edc905a079a70cfa5f3820bc76139fc91d6f9abc27ea2a887139" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand 0.8.7", + "serde", + "sha2 0.9.9", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + [[package]] name = "light-poseidon" version = "0.2.0" @@ -1107,7 +1517,7 @@ checksum = "3c9a85a9752c549ceb7578064b4ed891179d20acd85f27318573b64d2d7ee7ee" dependencies = [ "ark-bn254 0.4.0", "ark-ff 0.4.2", - "num-bigint", + "num-bigint 0.4.8", "thiserror 1.0.69", ] @@ -1119,17 +1529,81 @@ checksum = "47a1ccadd0bb5a32c196da536fd72c59183de24a055f6bf0513bf845fefab862" dependencies = [ "ark-bn254 0.5.0", "ark-ff 0.5.0", - "num-bigint", + "num-bigint 0.4.8", "thiserror 1.0.69", ] [[package]] -name = "lock_api" -version = "0.4.14" +name = "litesvm" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +checksum = "e5c571b8f010af084d125c8968e52658eb2c5d146a0a8254681adcdac36caa3e" dependencies = [ - "scopeguard", + "agave-feature-set", + "agave-reserved-account-keys", + "ansi_term", + "indexmap", + "itertools 0.14.0", + "log", + "serde", + "solana-account", + "solana-address 2.6.1", + "solana-address-lookup-table-interface", + "solana-builtins", + "solana-clock", + "solana-compute-budget", + "solana-compute-budget-instruction", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-feature-gate-interface", + "solana-fee", + "solana-fee-structure", + "solana-hash", + "solana-instruction", + "solana-instruction-error", + "solana-instructions-sysvar 4.0.0", + "solana-keypair", + "solana-last-restart-slot", + "solana-loader-v3-interface 8.0.1", + "solana-loader-v4-interface", + "solana-message", + "solana-native-token", + "solana-nonce", + "solana-nonce-account", + "solana-precompile-error", + "solana-program-error", + "solana-program-runtime", + "solana-rent", + "solana-sdk-ids", + "solana-sha256-hasher", + "solana-signature", + "solana-signer", + "solana-slot-hashes", + "solana-slot-history", + "solana-stake-history", + "solana-svm-callback", + "solana-svm-log-collector", + "solana-svm-timings", + "solana-svm-transaction", + "solana-syscalls", + "solana-system-interface", + "solana-system-program", + "solana-sysvar", + "solana-sysvar-id", + "solana-transaction", + "solana-transaction-context", + "solana-transaction-error", + "thiserror 2.0.19", + "wincode", +] + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", ] [[package]] @@ -1161,7 +1635,7 @@ checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" dependencies = [ "byteorder", "keccak", - "rand_core", + "rand_core 0.6.4", "zeroize", ] @@ -1198,7 +1672,7 @@ dependencies = [ "mirror-provenance", "serde", "serde_json", - "sha2", + "sha2 0.10.9", ] [[package]] @@ -1213,10 +1687,17 @@ dependencies = [ name = "mirror-pool-program" version = "0.1.0" dependencies = [ + "ark-std 0.5.0", "groth16-solana", + "litesvm", + "mirror-circuit", "mirror-core", + "solana-keypair", + "solana-message", "solana-program", + "solana-signer", "solana-system-interface", + "solana-transaction", ] [[package]] @@ -1229,6 +1710,31 @@ dependencies = [ "thiserror 2.0.19", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint 0.2.6", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.8" @@ -1239,6 +1745,27 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "num-integer" version = "0.1.46" @@ -1248,6 +1775,28 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint 0.2.6", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1257,6 +1806,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + [[package]] name = "once_cell" version = "1.21.4" @@ -1269,6 +1828,21 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1304,6 +1878,30 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ee67f1008b1ba2321834326597b8e186293b049a023cdef258527550b9935b4" +[[package]] +name = "pbkdf2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "percentage" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd23b938276f14057220b707937bcb42fa76dda7560e57a2da30cb52d557937" +dependencies = [ + "num", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -1320,6 +1918,18 @@ dependencies = [ "spki", ] +[[package]] +name = "polyval" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1347,6 +1957,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "qstring" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d464fae65fff2680baf48019211ce37aaec0c78e9264c84a3e484717f965104e" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "qualifier_attr" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "quote" version = "1.0.47" @@ -1356,14 +1986,37 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[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.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" dependencies = [ - "rand_chacha", - "rand_core", + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1373,7 +2026,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1382,7 +2045,25 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", ] [[package]] @@ -1404,6 +2085,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "rustc-demangle" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b74b56ffa8bb2830709a538c2cbcae9aa062db0d2a42563bfb09bdaae44020eb" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1449,6 +2136,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -1482,6 +2179,41 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "serde_core", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.9.0", + "opaque-debug", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1490,7 +2222,7 @@ checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures 0.2.17", - "digest", + "digest 0.10.7", ] [[package]] @@ -1505,7 +2237,7 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ - "digest", + "digest 0.10.7", "keccak", ] @@ -1521,8 +2253,8 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest", - "rand_core", + "digest 0.10.7", + "rand_core 0.6.4", ] [[package]] @@ -1531,6 +2263,24 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +[[package]] +name = "solana-account" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385cad928d576683db3afef1b88d00a31a7126cc9f6f3f0c9f6b2d181437f53c" +dependencies = [ + "bincode", + "serde", + "serde_bytes", + "serde_derive", + "solana-account-info", + "solana-clock", + "solana-instruction-error", + "solana-pubkey 4.2.0", + "solana-sdk-ids", + "solana-sysvar", +] + [[package]] name = "solana-account-info" version = "3.1.1" @@ -1539,16 +2289,25 @@ checksum = "a9cf16495d9eb53e3d04e72366a33bb1c20c24e78c171d8b8f5978357b63ae95" dependencies = [ "bincode", "serde_core", - "solana-address", + "solana-address 2.6.1", "solana-program-error", "solana-program-memory", ] [[package]] name = "solana-address" -version = "2.7.0" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2ecac8e1b7f74c2baa9e774c42817e3e75b20787134b76cc4d45e8a604488f5" +dependencies = [ + "solana-address 2.6.1", +] + +[[package]] +name = "solana-address" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01332a01c0a3098404d55a724c8d9a92aed4a50fe40a7dd0c7a51e29274c14de" +checksum = "39c93e262f671bf402e1040e4a7e40b05d81da5956c7681948c975a0997517bb" dependencies = [ "borsh", "bytemuck", @@ -1567,6 +2326,24 @@ dependencies = [ "wincode", ] +[[package]] +name = "solana-address-lookup-table-interface" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115b4f773acc4f3f3cb986b0d335e9845c0368c82b0940410935bc11ae065578" +dependencies = [ + "bincode", + "bytemuck", + "serde", + "serde_derive", + "solana-clock", + "solana-instruction", + "solana-instruction-error", + "solana-pubkey 4.2.0", + "solana-sdk-ids", + "solana-slot-hashes", +] + [[package]] name = "solana-atomic-u64" version = "3.0.1" @@ -1582,11 +2359,22 @@ version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30c80fb6d791b3925d5ec4bf23a7c169ef5090c013059ec3ed7d0b2c04efa085" dependencies = [ - "num-bigint", + "num-bigint 0.4.8", "num-traits", "solana-define-syscall 3.0.0", ] +[[package]] +name = "solana-bincode" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278a1a5bad62cd9da89ac8d4b7ec444e83caa8ae96aa656dfc27684b28d49a5d" +dependencies = [ + "bincode", + "serde_core", + "solana-instruction-error", +] + [[package]] name = "solana-blake3-hasher" version = "3.1.0" @@ -1599,20 +2387,71 @@ dependencies = [ ] [[package]] -name = "solana-bn254" -version = "2.2.2" +name = "solana-bls-signatures" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4420f125118732833f36facf96a27e7b78314b2d642ba07fa9ffdacd8d79e243" +checksum = "654ea8d31bbb4b0b9c28f9c545edb32691de27d4e2fc0e252c1ff47da50c5f2a" dependencies = [ - "ark-bn254 0.4.0", - "ark-ec 0.4.2", - "ark-ff 0.4.2", + "base64", + "blst", + "blstrs", + "cfg_eval", + "ff", + "group", + "pairing", + "rand 0.8.7", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.19", + "wincode", + "zeroize", +] + +[[package]] +name = "solana-bls12-381-syscall" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a699aa5e660203c17c18ceaef0eff78c67fd5193f4dbe90a07148e06030ab6" +dependencies = [ + "blst", + "blstrs", + "bytemuck", + "bytemuck_derive", + "group", + "pairing", +] + +[[package]] +name = "solana-bn254" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4420f125118732833f36facf96a27e7b78314b2d642ba07fa9ffdacd8d79e243" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", "ark-serialize 0.4.2", "bytemuck", "solana-define-syscall 2.3.0", "thiserror 2.0.19", ] +[[package]] +name = "solana-bn254" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ff13a8867fcc7b0f1114764e1bf6191b4551dcaf93729ddc676cd4ec6abc9f" +dependencies = [ + "ark-bn254 0.5.0", + "ark-ec 0.5.0", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "bytemuck", + "solana-define-syscall 5.1.0", + "thiserror 2.0.19", +] + [[package]] name = "solana-borsh" version = "3.0.2" @@ -1622,11 +2461,69 @@ dependencies = [ "borsh", ] +[[package]] +name = "solana-bpf-loader-program" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "073e0a6555480e8f783f930299a5660c2296c8961d9b3a2adaf585561e2d59b9" +dependencies = [ + "bincode", + "qualifier_attr", + "solana-account", + "solana-bincode", + "solana-clock", + "solana-instruction", + "solana-loader-v3-interface 7.0.0", + "solana-packet", + "solana-program-runtime", + "solana-pubkey 4.2.0", + "solana-sbpf", + "solana-sdk-ids", + "solana-svm-feature-set", + "solana-svm-log-collector", + "solana-svm-measure", + "solana-svm-type-overrides", + "solana-syscalls", + "solana-system-interface", + "solana-transaction-context", +] + +[[package]] +name = "solana-builtins" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0e54d03edadcb2f38bdb8b6524380a858f5ad51654db1f71eccc3fb5dc4d492" +dependencies = [ + "agave-feature-set", + "solana-bpf-loader-program", + "solana-compute-budget-program", + "solana-hash", + "solana-program-runtime", + "solana-pubkey 4.2.0", + "solana-sdk-ids", + "solana-system-program", + "solana-vote-program", + "solana-zk-elgamal-proof-program", + "solana-zk-token-proof-program", +] + +[[package]] +name = "solana-builtins-default-costs" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a63b9b3ae7b09c32f7f15d83e875dbf8715d0853c2354e3306316158f19d9c90" +dependencies = [ + "agave-feature-set", + "ahash", + "solana-pubkey 4.2.0", + "solana-sdk-ids", +] + [[package]] name = "solana-clock" -version = "3.2.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7708bdb262fd9c257c3a56d4289297b10a3e821b8cba3f7cf42b49c40201ab9" +checksum = "f0acdace90d96e2c9e70d681465b4fe888b6bcf27c354ae9774e9f8a3b72923d" dependencies = [ "serde", "serde_derive", @@ -1634,6 +2531,56 @@ dependencies = [ "solana-sdk-ids", "solana-sdk-macro", "solana-sysvar-id", + "wincode", +] + +[[package]] +name = "solana-compute-budget" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1770e5aeba2f5ec75b2d89031780c760dcf8352a061430f5e74eab1ad9f3b132" +dependencies = [ + "solana-fee-structure", + "solana-program-runtime", +] + +[[package]] +name = "solana-compute-budget-instruction" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3ad15d715b0c4e21f6b591f126b7fa8070aadf0d75fcd89bc4c98afc9e5f8f6" +dependencies = [ + "agave-feature-set", + "solana-borsh", + "solana-builtins-default-costs", + "solana-compute-budget", + "solana-compute-budget-interface", + "solana-instruction", + "solana-packet", + "solana-pubkey 4.2.0", + "solana-sdk-ids", + "solana-svm-transaction", + "solana-transaction-error", +] + +[[package]] +name = "solana-compute-budget-interface" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8292c436b269ad23cecc8b24f7da3ab07ca111661e25e00ce0e1d22771951ab9" +dependencies = [ + "borsh", + "solana-instruction", + "solana-sdk-ids", +] + +[[package]] +name = "solana-compute-budget-program" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653943375fb64b11bcfda0c22280aa87f04902d2c25d0117e91f9788a31ed3db" +dependencies = [ + "solana-program-runtime", ] [[package]] @@ -1646,10 +2593,24 @@ dependencies = [ "solana-define-syscall 4.0.1", "solana-instruction", "solana-program-error", - "solana-pubkey", + "solana-pubkey 4.2.0", "solana-stable-layout", ] +[[package]] +name = "solana-curve25519" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b4d2a4bf0d0b0a86c22111917e86e8bd39a7b31420fb2c7d73eb83761fc7af" +dependencies = [ + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "solana-define-syscall 5.1.0", + "subtle", + "thiserror 2.0.19", +] + [[package]] name = "solana-define-syscall" version = "2.3.0" @@ -1674,11 +2635,22 @@ version = "5.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21e14a4f604117f379840956a8fc8695e4c84f5b0ebed192f31f60d9b85d581d" +[[package]] +name = "solana-derivation-path" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff71743072690fdbdfcdc37700ae1cb77485aaad49019473a81aee099b1e0b8c" +dependencies = [ + "derivation-path", + "qstring", + "uriparse", +] + [[package]] name = "solana-epoch-rewards" -version = "3.2.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0788d74ee15778deecaa15ed1a1e37727ba954f86cbc35225450a1f2b5012969" +checksum = "daf7eb4986b0b1d6f562b21f75a836f1a6df6e00c275efcef50aab5c144dc59e" dependencies = [ "serde", "serde_derive", @@ -1687,6 +2659,7 @@ dependencies = [ "solana-sdk-ids", "solana-sdk-macro", "solana-sysvar-id", + "wincode", ] [[package]] @@ -1702,6 +2675,7 @@ dependencies = [ "solana-sdk-ids", "solana-sdk-macro", "solana-sysvar-id", + "wincode", ] [[package]] @@ -1711,7 +2685,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "027e6d0b9e7daac5b2ac7c3f9ca1b727861121d9ef05084cf435ff736051e7c2" dependencies = [ "solana-define-syscall 5.1.0", - "solana-pubkey", + "solana-pubkey 4.2.0", ] [[package]] @@ -1725,12 +2699,42 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-nonce", - "solana-pubkey", + "solana-pubkey 4.2.0", "solana-sdk-ids", "solana-system-interface", "thiserror 2.0.19", ] +[[package]] +name = "solana-feature-gate-interface" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd7545e02f91da1d6996f32b18f7796aa01e0682f8f3a7434b82cd1a10448add" +dependencies = [ + "bincode", + "serde", + "serde_derive", + "solana-account", + "solana-account-info", + "solana-instruction", + "solana-program-error", + "solana-pubkey 4.2.0", + "solana-rent", + "solana-sdk-ids", + "solana-system-interface", +] + +[[package]] +name = "solana-fee" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5fbffe004ec900267fc0bcf0c3b532c01905e6c4e3f318562718d1cdf6db939" +dependencies = [ + "agave-feature-set", + "solana-fee-structure", + "solana-svm-transaction", +] + [[package]] name = "solana-fee-calculator" version = "3.2.2" @@ -1740,24 +2744,31 @@ dependencies = [ "log", "serde", "serde_derive", + "wincode", ] +[[package]] +name = "solana-fee-structure" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2abdb1223eea8ec64136f39cb1ffcf257e00f915c957c35c0dd9e3f4e700b0" + [[package]] name = "solana-get-sysvar" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef3bc859fc036ed490146793557386cbfae614ebba4adc704c37d94350824ed4" dependencies = [ - "solana-address", + "solana-address 2.6.1", "solana-define-syscall 5.1.0", "solana-program-error", ] [[package]] name = "solana-hash" -version = "4.6.0" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df9b01495ed31100aca97a7f5862d5e19ab1636d60d1a9f02391408dd9dec84" +checksum = "fe51db00ac3aa9f950d1e6201a126acfa26e6d81bc4a183ba64ec02effcad883" dependencies = [ "borsh", "bytemuck", @@ -1765,8 +2776,16 @@ dependencies = [ "five8", "serde", "serde_derive", + "solana-sanitize", + "wincode", ] +[[package]] +name = "solana-hash-512" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee9c987913768643be3f4fc5f8ec667e8ec19e59e6d131688ade8d1430dafe9" + [[package]] name = "solana-instruction" version = "3.4.0" @@ -1779,7 +2798,8 @@ dependencies = [ "serde_derive", "solana-define-syscall 5.1.0", "solana-instruction-error", - "solana-pubkey", + "solana-pubkey 4.2.0", + "wincode", ] [[package]] @@ -1809,6 +2829,23 @@ dependencies = [ "solana-sysvar-id", ] +[[package]] +name = "solana-instructions-sysvar" +version = "4.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e38363a181313d607f7a118df2b401bb27b477a0104b09283cbf504f5f0f00cc" +dependencies = [ + "bitflags", + "solana-account-info", + "solana-instruction", + "solana-instruction-error", + "solana-program-error", + "solana-sanitize", + "solana-sdk-ids", + "solana-serialize-utils", + "solana-sysvar-id", +] + [[package]] name = "solana-keccak-hasher" version = "3.1.0" @@ -1820,11 +2857,27 @@ dependencies = [ "solana-hash", ] +[[package]] +name = "solana-keypair" +version = "3.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "263d614c12aa267a3278703175fd6440552ca61bc960b5a02a4482720c53438b" +dependencies = [ + "ed25519-dalek", + "five8", + "five8_core", + "rand 0.9.5", + "solana-address 2.6.1", + "solana-seed-phrase", + "solana-signature", + "solana-signer", +] + [[package]] name = "solana-last-restart-slot" -version = "3.2.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5099e736c3c06c451b307a60637d69eb65b3144143ebeed899e2fd1134f4fc35" +checksum = "c22474b83d3c7c318e1c3a725784fc2d1d03b728e36369e58ce48769a61ed85e" dependencies = [ "serde", "serde_derive", @@ -1832,6 +2885,62 @@ dependencies = [ "solana-sdk-ids", "solana-sdk-macro", "solana-sysvar-id", + "wincode", +] + +[[package]] +name = "solana-loader-v3-interface" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68029ab11d9c891d4ce23ada75745e40f983c5674af366f447b672569634231b" +dependencies = [ + "serde", + "serde_bytes", + "serde_derive", + "solana-instruction", + "solana-pubkey 4.2.0", + "solana-sdk-ids", +] + +[[package]] +name = "solana-loader-v3-interface" +version = "8.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "362b468fbde4c20521b9ff5ef743bc0d9b410455a5a92a8b29fd46954e89345f" +dependencies = [ + "solana-instruction", + "solana-pubkey 4.2.0", + "solana-sdk-ids", + "solana-system-interface", + "wincode", +] + +[[package]] +name = "solana-loader-v4-interface" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4c948b33ff81fa89699911b207059e493defdba9647eaf18f23abdf3674e0fb" +dependencies = [ + "solana-instruction", + "solana-pubkey 3.0.0", + "solana-sdk-ids", +] + +[[package]] +name = "solana-message" +version = "4.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2666e0ff38557ad1239d0fc42f4688fb9df0817fb12c041dbae90c2b2e3fa55" +dependencies = [ + "blake3", + "solana-address 2.6.1", + "solana-hash", + "solana-instruction", + "solana-sanitize", + "solana-sdk-ids", + "solana-short-vec", + "solana-transaction-error", + "wincode", ] [[package]] @@ -1855,10 +2964,36 @@ version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4172d5b33a0a38fcdb2af8f84406570dd51567267da96c28ad0f31fc11621c0" dependencies = [ + "serde", + "serde_derive", "solana-fee-calculator", "solana-hash", - "solana-pubkey", + "solana-pubkey 4.2.0", "solana-sha256-hasher", + "wincode", +] + +[[package]] +name = "solana-nonce-account" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b81bf7e2aa8c443724051507c1007d0b6d9c34b70925d3232dc78f5ca485209e" +dependencies = [ + "solana-account", + "solana-hash", + "solana-nonce", + "solana-sdk-ids", + "wincode", +] + +[[package]] +name = "solana-packet" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2a582d7863548f047f49aa3745c076cfa9e1364baa080ef0c1210f0e4ebda" +dependencies = [ + "bitflags", + "solana-pubkey 4.2.0", ] [[package]] @@ -1875,6 +3010,15 @@ dependencies = [ "thiserror 2.0.19", ] +[[package]] +name = "solana-precompile-error" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cafcd950de74c6c39d55dc8ca108bbb007799842ab370ef26cf45a34453c31e1" +dependencies = [ + "num-traits", +] + [[package]] name = "solana-program" version = "4.0.0" @@ -1897,7 +3041,7 @@ dependencies = [ "solana-hash", "solana-instruction", "solana-instruction-error", - "solana-instructions-sysvar", + "solana-instructions-sysvar 3.0.1", "solana-keccak-hasher", "solana-last-restart-slot", "solana-msg", @@ -1907,7 +3051,7 @@ dependencies = [ "solana-program-memory", "solana-program-option", "solana-program-pack", - "solana-pubkey", + "solana-pubkey 4.2.0", "solana-rent", "solana-sdk-ids", "solana-secp256k1-recover", @@ -1931,7 +3075,7 @@ dependencies = [ "solana-account-info", "solana-define-syscall 4.0.1", "solana-program-error", - "solana-pubkey", + "solana-pubkey 4.2.0", ] [[package]] @@ -1970,19 +3114,74 @@ dependencies = [ ] [[package]] -name = "solana-pubkey" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7db719574990de7e8b0f55a8593ac92a5ccb42c8ce67b3e4bf05b139d5d9ee71" -dependencies = [ - "solana-address", -] - -[[package]] -name = "solana-rent" -version = "4.3.0" +name = "solana-program-runtime" +version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f0d780bf8e8a1fe8b5b5fce1acad6b209485b86dec246e7523d5e4a8b7c7fc" +checksum = "d0364032ca71f9be2ffab27791cd6535dbe9d2990da0e2346e99013a9bcb57f4" +dependencies = [ + "base64", + "bincode", + "cfg-if", + "itertools 0.14.0", + "log", + "percentage", + "rand 0.9.5", + "serde", + "solana-account", + "solana-account-info", + "solana-clock", + "solana-epoch-rewards", + "solana-epoch-schedule", + "solana-fee-structure", + "solana-hash", + "solana-instruction", + "solana-last-restart-slot", + "solana-loader-v3-interface 7.0.0", + "solana-program-entrypoint", + "solana-pubkey 4.2.0", + "solana-rent", + "solana-sbpf", + "solana-sdk-ids", + "solana-slot-hashes", + "solana-stable-layout", + "solana-stake-interface", + "solana-svm-callback", + "solana-svm-feature-set", + "solana-svm-log-collector", + "solana-svm-measure", + "solana-svm-timings", + "solana-svm-transaction", + "solana-svm-type-overrides", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", + "solana-transaction-context", + "thiserror 2.0.19", +] + +[[package]] +name = "solana-pubkey" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8909d399deb0851aa524420beeb5646b115fd253ef446e35fe4504c904da3941" +dependencies = [ + "solana-address 1.1.0", +] + +[[package]] +name = "solana-pubkey" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7db719574990de7e8b0f55a8593ac92a5ccb42c8ce67b3e4bf05b139d5d9ee71" +dependencies = [ + "solana-address 2.6.1", +] + +[[package]] +name = "solana-rent" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f0d780bf8e8a1fe8b5b5fce1acad6b209485b86dec246e7523d5e4a8b7c7fc" dependencies = [ "serde", "serde_derive", @@ -1990,6 +3189,7 @@ dependencies = [ "solana-sdk-ids", "solana-sdk-macro", "solana-sysvar-id", + "wincode", ] [[package]] @@ -1998,13 +3198,30 @@ version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" +[[package]] +name = "solana-sbpf" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f84c593fa3d4131045b606dec5acf9d8eac73791bc786ca9911057aec8f43ec" +dependencies = [ + "byteorder", + "combine", + "hash32", + "libc", + "log", + "rand 0.8.7", + "rustc-demangle", + "thiserror 2.0.19", + "winapi", +] + [[package]] name = "solana-sdk-ids" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "def234c1956ff616d46c9dd953f251fa7096ddbaa6d52b165218de97882b7280" dependencies = [ - "solana-address", + "solana-address 2.6.1", ] [[package]] @@ -2030,6 +3247,26 @@ dependencies = [ "thiserror 2.0.19", ] +[[package]] +name = "solana-seed-derivable" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff7bdb72758e3bec33ed0e2658a920f1f35dfb9ed576b951d20d63cb61ecd95c" +dependencies = [ + "solana-derivation-path", +] + +[[package]] +name = "solana-seed-phrase" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc905b200a95f2ea9146e43f2a7181e3aeb55de6bc12afb36462d00a3c7310de" +dependencies = [ + "hmac", + "pbkdf2", + "sha2 0.10.9", +] + [[package]] name = "solana-serde-varint" version = "3.0.1" @@ -2046,7 +3283,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "761357b0853c9623bf12c1d2314b3d6160a85b087b84c45224fb85766d22616b" dependencies = [ "solana-instruction-error", - "solana-pubkey", + "solana-pubkey 4.2.0", "solana-sanitize", ] @@ -2056,22 +3293,61 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db7dc3011ea4c0334aaaa7e7128cb390ecf546b28d412e9bf2064680f57f588f" dependencies = [ - "sha2", + "sha2 0.10.9", "solana-define-syscall 4.0.1", "solana-hash", ] +[[package]] +name = "solana-sha512-hasher" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11e10e103ab5bd52af341e7bc2f4123a2f4e66bb7ba4e6ca40f1e00cd6314e02" +dependencies = [ + "sha2 0.10.9", + "solana-define-syscall 5.1.0", + "solana-hash-512", +] + [[package]] name = "solana-short-vec" -version = "3.3.0" +version = "3.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8250a4495aad49ad20556a607da53bdcb20de78da10b65afbf918b7f1de647" +dependencies = [ + "serde_core", + "wincode", +] + +[[package]] +name = "solana-signature" +version = "3.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0364c7577c3c82a693ce28a1febc8d1b5d1b0a175fdc2114ae6186b69effe1e" +dependencies = [ + "ed25519-dalek", + "five8", + "serde", + "solana-sanitize", + "wincode", +] + +[[package]] +name = "solana-signer" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75782529cc4521114c1ecd71c7e2cdebd41a6fe9844b5d088fd36ef08c57c36" +checksum = "520bd6021163ee517f4bdc7ae03ded904f97e11320001ba0b3355f45eb14f558" +dependencies = [ + "solana-pubkey 4.2.0", + "solana-signature", + "solana-transaction-error", +] [[package]] name = "solana-slot-hashes" -version = "3.2.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "007d6bc909599eea325c9d09f7ff16ef6166c134876ff47a6a122d693d058dad" +checksum = "5c7ce2b4b8911bf2db3de7b6266e67bfc21a6a9f8c566fb096d9782ca2ad16ee" dependencies = [ "serde", "serde_derive", @@ -2079,13 +3355,14 @@ dependencies = [ "solana-hash", "solana-sdk-ids", "solana-sysvar-id", + "wincode", ] [[package]] name = "solana-slot-history" -version = "3.2.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4560fde841a6f2001aedc33b74fe99840ee3d56a71fe9b98ee332303b8597900" +checksum = "40427c04d3e808493cb5e3d1a97cef84d7c15cb6f89b15c5684d0d4027105600" dependencies = [ "bv", "serde", @@ -2093,6 +3370,7 @@ dependencies = [ "solana-get-sysvar", "solana-sdk-ids", "solana-sysvar-id", + "wincode", ] [[package]] @@ -2102,7 +3380,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9f6a291ba063a37780af29e7db14bdd3dc447584d8ba5b3fc4b88e2bbc982fa" dependencies = [ "solana-instruction", - "solana-pubkey", + "solana-pubkey 4.2.0", ] [[package]] @@ -2117,6 +3395,135 @@ dependencies = [ "solana-get-sysvar", "solana-sdk-ids", "solana-sysvar-id", + "wincode", +] + +[[package]] +name = "solana-stake-interface" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f49eb5c77484214c3484921e2cdda79d185373118b5458c1b2df0f1a04c3bc30" +dependencies = [ + "num-traits", + "serde", + "serde_derive", + "solana-clock", + "solana-cpi", + "solana-instruction", + "solana-program-error", + "solana-pubkey 4.2.0", + "solana-system-interface", + "solana-sysvar", + "solana-sysvar-id", +] + +[[package]] +name = "solana-svm-callback" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d50ab466a202b3ec768d0d1f808c570822b4d2492801772359b9509baf1e8c6" +dependencies = [ + "solana-account", + "solana-clock", + "solana-precompile-error", + "solana-pubkey 4.2.0", +] + +[[package]] +name = "solana-svm-feature-set" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35320ec1b4460e2f748c9ebb6c42eb3069f8d85ec46a185ae9b5628430f3c606" + +[[package]] +name = "solana-svm-log-collector" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5ba1a825b27776f6a92fae44613d5e7f906f0c56e542bbbe1a6746eb168daff" +dependencies = [ + "log", +] + +[[package]] +name = "solana-svm-measure" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ec9c705bec0fc99b9220e62fbae9fb0a297b9a95ff411badce934103a92ec7" + +[[package]] +name = "solana-svm-timings" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b72896172acdbce474662e876fdd413092893599156500cc68840abea649441" +dependencies = [ + "eager", + "enum-iterator", + "solana-pubkey 4.2.0", +] + +[[package]] +name = "solana-svm-transaction" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "998e6d7d1197d29a77d3701ae273cfc3e95e79978546faa4d28af637ef5211e7" +dependencies = [ + "solana-hash", + "solana-message", + "solana-pubkey 4.2.0", + "solana-sdk-ids", + "solana-signature", + "solana-transaction", +] + +[[package]] +name = "solana-svm-type-overrides" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128cad78148dbd90e61a0bdf54706a24c356e858d85f2674dd7755eac594a4df" +dependencies = [ + "rand 0.9.5", +] + +[[package]] +name = "solana-syscalls" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2788d359af16fac879c081bc0b725e8a200702156af92e75e2c18346fcde332" +dependencies = [ + "bincode", + "libsecp256k1", + "num-traits", + "solana-account", + "solana-account-info", + "solana-big-mod-exp", + "solana-blake3-hasher", + "solana-bls12-381-syscall", + "solana-bn254 3.2.1", + "solana-clock", + "solana-cpi", + "solana-curve25519", + "solana-hash", + "solana-hash-512", + "solana-instruction", + "solana-keccak-hasher", + "solana-poseidon", + "solana-program-entrypoint", + "solana-program-runtime", + "solana-pubkey 4.2.0", + "solana-sbpf", + "solana-sdk-ids", + "solana-secp256k1-recover", + "solana-sha256-hasher", + "solana-sha512-hasher", + "solana-stable-layout", + "solana-stake-interface", + "solana-svm-feature-set", + "solana-svm-log-collector", + "solana-svm-type-overrides", + "solana-sysvar", + "solana-sysvar-id", + "solana-transaction-context", + "thiserror 2.0.19", ] [[package]] @@ -2128,10 +3535,35 @@ dependencies = [ "num-traits", "serde", "serde_derive", - "solana-address", + "solana-address 2.6.1", "solana-instruction", "solana-msg", "solana-program-error", + "wincode", +] + +[[package]] +name = "solana-system-program" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64154747d8e97a2ac1869e7633b9d5b8cac01d1ca99cf05f5910eb4b5718fbbc" +dependencies = [ + "bincode", + "log", + "solana-account", + "solana-bincode", + "solana-fee-calculator", + "solana-instruction", + "solana-nonce", + "solana-nonce-account", + "solana-packet", + "solana-program-runtime", + "solana-pubkey 4.2.0", + "solana-sdk-ids", + "solana-svm-log-collector", + "solana-system-interface", + "solana-sysvar", + "solana-transaction-context", ] [[package]] @@ -2160,7 +3592,7 @@ dependencies = [ "solana-program-entrypoint", "solana-program-error", "solana-program-memory", - "solana-pubkey", + "solana-pubkey 4.2.0", "solana-rent", "solana-sdk-ids", "solana-sdk-macro", @@ -2168,6 +3600,7 @@ dependencies = [ "solana-slot-history", "solana-stake-history", "solana-sysvar-id", + "wincode", ] [[package]] @@ -2176,8 +3609,165 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17358d1e9a13e5b9c2264d301102126cf11a47fd394cdf3dec174fe7bc96e1de" dependencies = [ - "solana-address", + "solana-address 2.6.1", + "solana-sdk-ids", +] + +[[package]] +name = "solana-transaction" +version = "4.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aa9b0f8543b99e1cb7db16a7c1a392139d82db57a90d262fb0a394807337bec" +dependencies = [ + "solana-address 2.6.1", + "solana-hash", + "solana-instruction", + "solana-instruction-error", + "solana-message", + "solana-sanitize", + "solana-sdk-ids", + "solana-short-vec", + "solana-signature", + "solana-signer", + "solana-transaction-error", + "wincode", +] + +[[package]] +name = "solana-transaction-context" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffd061d881fb2c182078e6ca6094fe113e50edbec224c4f8999747eb643cd02" +dependencies = [ + "bincode", + "serde", + "solana-account", + "solana-instruction", + "solana-instructions-sysvar 3.0.1", + "solana-pubkey 4.2.0", + "solana-rent", + "solana-sbpf", + "solana-sdk-ids", +] + +[[package]] +name = "solana-transaction-error" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a37cf28c58b03878d38c38411f0414d3a99a9e3b7e5ebb0f83f3317b2796d18" +dependencies = [ + "solana-instruction-error", + "solana-sanitize", +] + +[[package]] +name = "solana-vote-interface" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13a7a204b455a53fe8c9a26a6885391a5896835e06694dbe0cf37c580f82f128" +dependencies = [ + "bincode", + "cfg_eval", + "num-derive", + "num-traits", + "serde", + "serde_derive", + "serde_with", + "solana-clock", + "solana-hash", + "solana-instruction", + "solana-instruction-error", + "solana-pubkey 4.2.0", + "solana-rent", + "solana-sdk-ids", + "solana-serde-varint", + "solana-serialize-utils", + "solana-short-vec", + "solana-system-interface", +] + +[[package]] +name = "solana-vote-program" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d772aa219b97b0cd9ec512c9330179714e3bb6231df4a8b81981b6c88f85f1" +dependencies = [ + "agave-feature-set", + "bincode", + "log", + "solana-account", + "solana-bincode", + "solana-bls-signatures", + "solana-clock", + "solana-epoch-schedule", + "solana-hash", + "solana-instruction", + "solana-packet", + "solana-program-runtime", + "solana-pubkey 4.2.0", + "solana-rent", + "solana-sdk-ids", + "solana-slot-hashes", + "solana-system-interface", + "solana-transaction-context", + "solana-vote-interface", +] + +[[package]] +name = "solana-zk-elgamal-proof-program" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d841aa9791a275535f676c9403413c66da7c1bb1c867d3575b58f4e5b881ef" +dependencies = [ + "bytemuck", + "solana-instruction", + "solana-program-runtime", + "solana-sdk-ids", + "solana-svm-log-collector", + "solana-zk-sdk", +] + +[[package]] +name = "solana-zk-sdk" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09670ff59f60e6ddc2209c2e4353992a9b9f01d4e244f3e9d67bd5146e33d388" +dependencies = [ + "aes-gcm-siv", + "base64", + "bincode", + "bytemuck", + "bytemuck_derive", + "curve25519-dalek", + "itertools 0.14.0", + "merlin", + "num-derive", + "num-traits", + "rand 0.8.7", + "serde", + "serde_derive", + "serde_json", + "sha3", + "solana-address 2.6.1", + "solana-derivation-path", + "solana-instruction", "solana-sdk-ids", + "solana-seed-derivable", + "solana-seed-phrase", + "solana-signature", + "solana-signer", + "subtle", + "thiserror 2.0.19", + "zeroize", +] + +[[package]] +name = "solana-zk-token-proof-program" +version = "4.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4841d6e055847c84d011d187cbad022ea4f8832c85a61a1aa220c192bd4dfd0" +dependencies = [ + "solana-program-runtime", ] [[package]] @@ -2235,6 +3825,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "thiserror" version = "1.0.69" @@ -2275,6 +3871,15 @@ dependencies = [ "syn 3.0.3", ] +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + [[package]] name = "tinyvec" version = "1.12.0" @@ -2360,6 +3965,35 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.7", + "subtle", +] + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +dependencies = [ + "void", +] + +[[package]] +name = "uriparse" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200d0fc04d809396c2ad43f3c95da3582a2556eba8d453c1087f4120ee352ff" +dependencies = [ + "fnv", + "lazy_static", +] + [[package]] name = "utf8parse" version = "0.2.2" @@ -2372,18 +4006,56 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "wincode" -version = "0.6.0" +version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5d39d1a984eb7ae37afa348f058216d62a6d5f71640f4113a8114386c2a812a" +checksum = "66d967db7705dc29120bb6e8ce5b5a2e27734ed5976d1c904e95bd238d1c3c5a" dependencies = [ + "bv", "pastey", "proc-macro2", "quote", @@ -2393,9 +4065,9 @@ dependencies = [ [[package]] name = "wincode-derive" -version = "0.5.0" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb427b174d0f50b407f003623db1d892986e780651ee9d4231f3c9fe9ffcbb7" +checksum = "15ab90b719560d0fda79c74550ad1c948d17b118765942838055ebaf34d67071" dependencies = [ "darling", "proc-macro2", @@ -2427,6 +4099,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zerocopy" version = "0.8.55" diff --git a/Cargo.toml b/Cargo.toml index 1b5d80da..31055b67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,13 @@ serde_json = "1" clap = { version = "4", features = ["derive"] } sha2 = "0.10" hex = "0.4" +# Test-only: runs the built .so in a real SVM. 0.15 targets Agave 4.1.1, which is +# the toolchain the Makefile installs. +litesvm = "0.15" +solana-keypair = "3.1" +solana-signer = "3" +solana-transaction = "4.1" +solana-message = "4.2" mirror-core = { path = "crates/mirror-core" } mirror-circuit = { path = "crates/mirror-circuit" } diff --git a/programs/mirror-pool/Cargo.toml b/programs/mirror-pool/Cargo.toml index f04f71b0..304dc2ac 100644 --- a/programs/mirror-pool/Cargo.toml +++ b/programs/mirror-pool/Cargo.toml @@ -16,6 +16,16 @@ groth16-solana.workspace = true [features] no-entrypoint = [] +[dev-dependencies] +mirror-circuit.workspace = true +litesvm.workspace = true +solana-keypair.workspace = true +solana-signer.workspace = true +solana-transaction.workspace = true +solana-message.workspace = true +ark-std.workspace = true +solana-system-interface.workspace = true + # The entrypoint! macro emits these; declaring them keeps `-D warnings` honest # rather than silencing the lint wholesale. [lints.rust.unexpected_cfgs] diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index f807ecfa..096322a9 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -310,7 +310,18 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest return Err(MirrorProgramError::InvalidPda.into()); } - // Creation fails if the account already exists, which is the replay guard. + // The replay guard, checked explicitly. + // + // Creating the account would fail on its own if it already existed, but a + // failed CPI terminates the instruction with the *system program's* error, + // so the transaction would report a bare "already in use" rather than + // anything of ours. Checking first means a replay produces a named code that + // devnet evidence can record and a third party can verify, instead of an + // error that could have come from anywhere. + if !spend_account.data_is_empty() || spend_account.lamports() > 0 { + return Err(MirrorProgramError::NullifierAlreadySpent.into()); + } + let rent = Rent::get()?; invoke_signed( &system_instruction::create_account( diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs new file mode 100644 index 00000000..3c8f9869 --- /dev/null +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -0,0 +1,470 @@ +//! The whole protocol against the real compiled program. +//! +//! Every other test in this repository calls functions directly. This one loads +//! the `.so` that `make build-sbf` produces into a real SVM, sends real +//! transactions, and verifies a real Groth16 proof through the actual syscall. +//! It is the only test that can catch a divergence between what the host +//! believes and what the chain does. +//! +//! Requires `make build-sbf` first. If the artifact is missing the test says so +//! rather than silently passing, because a privacy test that quietly does +//! nothing is worse than no test. + +use litesvm::LiteSVM; +use mirror_circuit::{generate_reproducible, prove, Keys, Witness}; +use mirror_core::{Field, MerkleTree, Note}; +use mirror_pool_program::{ + instruction::Instruction as MirrorIx, + pda::{pool_address, spend_address, vault_address}, + spend::{Spend, SPEND_LEN, STATUS_PENDING}, + Pool, POOL_LEN, +}; +use solana_keypair::Keypair; +use solana_message::Message; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use solana_signer::Signer; +use solana_transaction::Transaction; + +const SEED: &[u8] = b"mirror-pool-reproducible-dev-setup-v1"; +const DENOMINATION: u64 = 100_000_000; // 0.1 SOL +const ENTRY_FEE: u64 = 0; +const K_FLOOR: u32 = 4; +const RELAY_FEE: u64 = 1_000_000; +const SELECTOR: u64 = 1; + +fn program_bytes() -> Vec { + let path = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../target/sbf/deploy/mirror_pool_program.so" + ); + std::fs::read(path).unwrap_or_else(|e| { + panic!("could not read {path}: {e}\n\nRun `make build-sbf` first."); + }) +} + +struct Env { + svm: LiteSVM, + program_id: Pubkey, + payer: Keypair, + pool: Pubkey, + vault: Pubkey, +} + +fn setup() -> Env { + let mut svm = LiteSVM::new(); + let program_id = Pubkey::new_unique(); + svm.add_program(program_id, &program_bytes()) + .expect("loading the built program"); + + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), 100_000_000_000).unwrap(); + + let (pool, _) = pool_address(&program_id, DENOMINATION); + let (vault, _) = vault_address(&program_id, &pool); + + Env { + svm, + program_id, + payer, + pool, + vault, + } +} + +impl Env { + fn send(&mut self, ix: Instruction, signer: &Keypair) -> Result<(), String> { + let msg = Message::new(&[ix], Some(&signer.pubkey())); + let tx = Transaction::new(&[signer], msg, self.svm.latest_blockhash()); + self.svm.send_transaction(tx).map(|_| ()).map_err(|e| { + format!( + "{:?} | logs: {:?}", + e.err, + e.meta.logs.iter().rev().take(4).collect::>() + ) + }) + } + + fn send_expect_cu(&mut self, ix: Instruction, signer: &Keypair) -> u64 { + let msg = Message::new(&[ix], Some(&signer.pubkey())); + let tx = Transaction::new(&[signer], msg, self.svm.latest_blockhash()); + let meta = self.svm.send_transaction(tx).unwrap_or_else(|e| { + panic!("transaction failed: {:?}\nlogs: {:#?}", e.err, e.meta.logs) + }); + meta.compute_units_consumed + } + + fn init_pool(&mut self) { + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::InitPool { + denomination: DENOMINATION, + entry_fee: ENTRY_FEE, + k_floor: K_FLOOR, + } + .pack(), + vec![ + AccountMeta::new(self.payer.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(self.vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let payer = self.payer.insecure_clone(); + self.send(ix, &payer).expect("init_pool"); + } + + fn deposit(&mut self, commitment: Field, depositor: &Keypair) -> Result<(), String> { + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::Deposit { + commitment: commitment.to_bytes(), + } + .pack(), + vec![ + AccountMeta::new(depositor.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(self.vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + self.send(ix, depositor) + } + + fn pool_state(&self) -> Vec { + self.svm.get_account(&self.pool).expect("pool exists").data + } + + fn vault_lamports(&self) -> u64 { + self.svm + .get_account(&self.vault) + .map(|a| a.lamports) + .unwrap_or(0) + } +} + +/// Builds a pool with `count` deposits and returns the host-side tree plus the +/// notes, so a proof can be produced for any of them. +fn seeded_pool(count: u64) -> (Env, MerkleTree, Vec, Keys) { + let mut env = setup(); + env.init_pool(); + + let denom_tag = Field::from_u64(DENOMINATION); + let mut tree = MerkleTree::new().unwrap(); + let mut notes = Vec::new(); + + for i in 1..=count { + let note = Note::new( + Field::from_u64(i * 1_000_003), + Field::from_u64(i * 7_919), + denom_tag, + ); + let depositor = Keypair::new(); + env.svm + .airdrop(&depositor.pubkey(), DENOMINATION + 10_000_000) + .unwrap(); + env.deposit(note.commitment().unwrap(), &depositor) + .unwrap_or_else(|e| panic!("deposit {i} failed: {e}")); + tree.insert(note.commitment().unwrap()).unwrap(); + notes.push(note); + } + + let keys = generate_reproducible(SEED).expect("setup"); + (env, tree, notes, keys) +} + +fn spend_ix( + env: &Env, + proof: &mirror_circuit::SolanaProof, + nullifier: [u8; 32], + beneficiary: &Pubkey, + relay: &Pubkey, +) -> Instruction { + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR, + beneficiary: beneficiary.to_bytes(), + relay_fee: RELAY_FEE, + } + .pack(), + vec![ + AccountMeta::new(*relay, true), + AccountMeta::new(env.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ) +} + +/// Produces a proof for note `index`, bound to `beneficiary`. +fn proof_for( + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + beneficiary: &Pubkey, +) -> mirror_circuit::SolanaProof { + use ark_std::rand::SeedableRng; + let merkle_proof = tree.proof(index as u64).unwrap(); + let binding = + mirror_core::action_binding(SELECTOR, &beneficiary.to_bytes(), RELAY_FEE).unwrap(); + let witness = Witness { + note: notes[index], + merkle_proof: &merkle_proof, + root: tree.root().unwrap(), + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([index as u8; 32]); + prove(keys, &witness, &mut rng).expect("proving") +} + +#[test] +fn a_pool_initialises_with_an_empty_accumulator() { + let mut env = setup(); + env.init_pool(); + + let mut data = env.pool_state(); + assert_eq!(data.len(), POOL_LEN); + let pool = Pool::load(&mut data).unwrap(); + assert_eq!(pool.denomination(), DENOMINATION); + assert_eq!(pool.k_floor(), K_FLOOR); + assert_eq!(pool.deposit_count(), 0); + assert_eq!( + pool.current_root().unwrap(), + mirror_core::Frontier::new().unwrap().root(), + "the deployed program and the host must start from one accumulator" + ); +} + +#[test] +fn deposits_escrow_exactly_the_denomination_and_advance_the_root() { + let (env, tree, _notes, _keys) = seeded_pool(4); + + let mut data = env.pool_state(); + let pool = Pool::load(&mut data).unwrap(); + assert_eq!(pool.deposit_count(), 4); + assert_eq!( + pool.current_root().unwrap(), + tree.root().unwrap(), + "the on-chain root diverged from the host tree" + ); + + // Escrow, plus the vault's own rent exemption. + assert!( + env.vault_lamports() >= 4 * DENOMINATION, + "vault holds {} for 4 notes of {DENOMINATION}", + env.vault_lamports() + ); + assert_eq!(pool.required_vault_lamports().unwrap(), 4 * DENOMINATION); +} + +/// The decisive test: a proof built by the host prover, verified by the real +/// Groth16 syscall inside the deployed program. +#[test] +fn a_real_proof_verifies_on_chain_and_records_the_spend() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + let proof = proof_for(&keys, &tree, ¬es, 2, &beneficiary); + let nullifier = proof.public_inputs[1]; + + let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &relay.pubkey()); + let cu = env.send_expect_cu(ix, &relay); + println!("submit_spend consumed {cu} compute units"); + assert!( + cu < 200_000, + "submit_spend used {cu} CU, above the default per-instruction budget" + ); + + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + let mut data = env + .svm + .get_account(&spend_pda) + .expect("spend recorded") + .data; + assert_eq!(data.len(), SPEND_LEN); + let record = Spend::load(&mut data).unwrap(); + assert_eq!(record.status(), STATUS_PENDING); + assert_eq!(record.selector(), SELECTOR); + assert_eq!(record.relay_fee(), RELAY_FEE); + assert_eq!(record.beneficiary(), beneficiary.to_bytes()); + assert_eq!( + record.relay(), + relay.pubkey().to_bytes(), + "the relay that submitted must be the one paid at settlement" + ); + + // No member key appears anywhere in this path. + assert_ne!(relay.pubkey(), beneficiary); +} + +#[test] +fn the_same_note_cannot_be_spent_twice() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let beneficiary = Pubkey::new_unique(); + let first_relay = Keypair::new(); + let second_relay = Keypair::new(); + env.svm + .airdrop(&first_relay.pubkey(), 10_000_000_000) + .unwrap(); + env.svm + .airdrop(&second_relay.pubkey(), 10_000_000_000) + .unwrap(); + + let proof = proof_for(&keys, &tree, ¬es, 1, &beneficiary); + let nullifier = proof.public_inputs[1]; + + let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &first_relay.pubkey()); + env.send(ix, &first_relay).expect("first spend"); + + // The replay goes through a *different* relay, so the transaction is not + // byte-identical and the runtime's duplicate-signature check cannot be what + // rejects it. The relay identity is not part of the action binding, so the + // proof itself is still entirely valid — the only thing standing in the way + // is the nullifier record. An earlier version of this test reused the first + // relay and passed for the wrong reason: it was rejected as AlreadyProcessed + // before the program ran at all. + let replay = spend_ix( + &env, + &proof, + nullifier, + &beneficiary, + &second_relay.pubkey(), + ); + let err = env + .send(replay, &second_relay) + .expect_err("a replayed proof was accepted"); + println!("replay rejected: {err}"); + assert!( + !err.contains("AlreadyProcessed"), + "the runtime deduplicated the transaction; the nullifier guard was never reached: {err}" + ); + assert!( + err.contains("Custom(15)"), + "expected NullifierAlreadySpent (15), got: {err}" + ); +} + +#[test] +fn a_relay_cannot_redirect_the_payout() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let honest_beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + let proof = proof_for(&keys, &tree, ¬es, 0, &honest_beneficiary); + let nullifier = proof.public_inputs[1]; + + // The relay swaps in its own address after the member proved. + let thief = Pubkey::new_unique(); + let ix = spend_ix(&env, &proof, nullifier, &thief, &relay.pubkey()); + let err = env + .send(ix, &relay) + .expect_err("a redirected payout was accepted"); + println!("redirect rejected: {err}"); +} + +#[test] +fn a_relay_cannot_inflate_its_own_fee() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + let proof = proof_for(&keys, &tree, ¬es, 3, &beneficiary); + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + + let ix = Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR, + beneficiary: beneficiary.to_bytes(), + relay_fee: RELAY_FEE * 50, // proved for RELAY_FEE + } + .pack(), + vec![ + AccountMeta::new(relay.pubkey(), true), + AccountMeta::new(env.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let err = env + .send(ix, &relay) + .expect_err("an inflated relay fee was accepted"); + println!("fee inflation rejected: {err}"); +} + +#[test] +fn a_proof_against_an_unknown_root_is_rejected() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + let mut proof = proof_for(&keys, &tree, ¬es, 4, &beneficiary); + let nullifier = proof.public_inputs[1]; + // A root the pool has never held. Flip a low bit so the value stays a + // canonical scalar and the rejection comes from the history check. + proof.public_inputs[0][31] ^= 1; + + let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &relay.pubkey()); + let err = env + .send(ix, &relay) + .expect_err("an unknown root was accepted"); + println!("unknown root rejected: {err}"); +} + +#[test] +fn a_pool_below_its_anonymity_floor_refuses_to_act() { + // Three deposits against a floor of four: acting here would give the member + // an anonymity set smaller than the pool promises. + let (mut env, tree, notes, keys) = seeded_pool(3); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + let proof = proof_for(&keys, &tree, ¬es, 0, &beneficiary); + let nullifier = proof.public_inputs[1]; + let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &relay.pubkey()); + let err = env + .send(ix, &relay) + .expect_err("a spend below the anonymity floor was accepted"); + println!("below-floor rejected: {err}"); +} + +#[test] +fn a_forged_proof_is_rejected() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + let mut proof = proof_for(&keys, &tree, ¬es, 2, &beneficiary); + let nullifier = proof.public_inputs[1]; + // Corrupt the proof itself. + proof.proof_a[63] ^= 1; + + let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &relay.pubkey()); + let err = env + .send(ix, &relay) + .expect_err("a corrupted proof was accepted"); + println!("forged proof rejected: {err}"); +} From 2e4ffb17ad23e27571136bd69d28b66c196ec2de Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 05:14:01 -0300 Subject: [PATCH 13/83] feat(program): settle_epoch, the synchronised crowd MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Executes a batch of pending spends in one transaction, so every payout shares a timestamp and an ordering and an observer watching beneficiaries receive funds cannot use arrival time to tell them apart. 9,402 compute units for four spends, because the expensive work already happened at submit. The crowd rule is conditional: a batch needs k_floor spends, or every spend in it must have waited out an hour. Requiring the crowd unconditionally would be a liveness hazard — a quiet pool could hold a member's funds until a crowd that never comes — and dropping it would make "synchronised" a word rather than a property. Settlement is permissionless, so a member can always settle their own batch once the timeout passes. Escrow is never hostage to anyone's liveness. The spend record now carries its pool and its submission time, so a record from one denomination cannot be presented to another pool's vault, and the timeout is checked against a value the program wrote rather than one a caller supplies. Vault lamports move by direct mutation rather than a system CPI, since the program owns the vault. No signer seeds and no nested invoke on the hot path. Negative cases, all with distinct codes: settling one pending spend twice inside a single batch (the double-spend an attacker reaches for first), replaying a settled batch, settling below the crowd size before the timeout, and paying a beneficiary the proof never bound. The harness now treats runtime deduplication as a test bug rather than a pass. Two settlement tests were failing this way — a byte-identical retry is rejected as AlreadyProcessed before the program runs, so the assertion proved nothing. `send` panics on it with an explanation instead, which closes the whole class rather than the two instances I happened to notice. --- programs/mirror-pool/src/error.rs | 2 + programs/mirror-pool/src/instruction.rs | 31 ++- programs/mirror-pool/src/processor.rs | 150 ++++++++++++++ programs/mirror-pool/src/spend.rs | 89 ++++++--- programs/mirror-pool/tests/end_to_end.rs | 242 ++++++++++++++++++++++- 5 files changed, 476 insertions(+), 38 deletions(-) diff --git a/programs/mirror-pool/src/error.rs b/programs/mirror-pool/src/error.rs index bacf1053..91982ffd 100644 --- a/programs/mirror-pool/src/error.rs +++ b/programs/mirror-pool/src/error.rs @@ -54,6 +54,8 @@ pub enum MirrorProgramError { BelowAnonymityFloor = 19, /// The relay fee is not less than the denomination. RelayFeeTooLarge = 20, + /// The batch is below the crowd size and has not waited out the timeout. + CrowdTooSmall = 21, } impl From for ProgramError { diff --git a/programs/mirror-pool/src/instruction.rs b/programs/mirror-pool/src/instruction.rs index f46e525a..846b6d71 100644 --- a/programs/mirror-pool/src/instruction.rs +++ b/programs/mirror-pool/src/instruction.rs @@ -19,6 +19,7 @@ pub enum Tag { InitPool = 0, Deposit = 1, SubmitSpend = 2, + SettleEpoch = 3, } impl Tag { @@ -27,6 +28,7 @@ impl Tag { 0 => Ok(Tag::InitPool), 1 => Ok(Tag::Deposit), 2 => Ok(Tag::SubmitSpend), + 3 => Ok(Tag::SettleEpoch), _ => Err(MirrorProgramError::MalformedInstruction), } } @@ -71,6 +73,13 @@ pub enum Instruction { beneficiary: [u8; 32], relay_fee: u64, }, + /// Executes a batch of pending spends in one transaction, so every payout in + /// an epoch shares a timestamp and an ordering. + /// + /// `count` is the number of spend records that follow in the account list. + /// Permissionless: anyone may settle, so no operator's absence can strand a + /// member's funds. + SettleEpoch { count: u8 }, } /// `InitPool`: tag + u64 + u64 + u32. @@ -79,6 +88,8 @@ pub const INIT_POOL_LEN: usize = 1 + 8 + 8 + 4; pub const DEPOSIT_LEN: usize = 1 + 32; /// `SubmitSpend`: tag + proof + root + nullifier + selector + beneficiary + fee. pub const SUBMIT_SPEND_LEN: usize = 1 + 64 + 128 + 64 + 32 + 32 + 8 + 32 + 8; +/// `SettleEpoch`: tag + count. +pub const SETTLE_EPOCH_LEN: usize = 1 + 1; fn read_u64(data: &[u8], at: usize) -> u64 { let mut b = [0u8; 8]; @@ -119,6 +130,12 @@ impl Instruction { commitment.copy_from_slice(&data[1..33]); Ok(Instruction::Deposit { commitment }) } + Tag::SettleEpoch => { + if data.len() != SETTLE_EPOCH_LEN { + return Err(MirrorProgramError::MalformedInstruction); + } + Ok(Instruction::SettleEpoch { count: data[1] }) + } Tag::SubmitSpend => { if data.len() != SUBMIT_SPEND_LEN { return Err(MirrorProgramError::MalformedInstruction); @@ -194,6 +211,9 @@ impl Instruction { out.extend_from_slice(&relay_fee.to_le_bytes()); out } + Instruction::SettleEpoch { count } => { + vec![Tag::SettleEpoch as u8, *count] + } } } } @@ -216,6 +236,10 @@ mod tests { } } + fn settle() -> Instruction { + Instruction::SettleEpoch { count: 7 } + } + fn submit_spend() -> Instruction { // Distinct byte patterns per field so a swapped offset cannot pass. Instruction::SubmitSpend { @@ -232,7 +256,7 @@ mod tests { #[test] fn packing_then_unpacking_is_the_identity() { - for ix in [init(), deposit(), submit_spend()] { + for ix in [init(), deposit(), submit_spend(), settle()] { assert_eq!(Instruction::unpack(&ix.pack()).unwrap(), ix); } } @@ -246,6 +270,7 @@ mod tests { assert_eq!(INIT_POOL_LEN, 21); assert_eq!(DEPOSIT_LEN, 33); assert_eq!(SUBMIT_SPEND_LEN, 369); + assert_eq!(settle().pack().len(), SETTLE_EPOCH_LEN); } #[test] @@ -258,7 +283,7 @@ mod tests { #[test] fn an_unknown_tag_is_refused() { - for tag in [3u8, 4, 99, 255] { + for tag in [4u8, 5, 99, 255] { assert!( matches!( Instruction::unpack(&[tag]), @@ -273,7 +298,7 @@ mod tests { /// buffer must not be zero-extended and a long one must not be truncated. #[test] fn every_wrong_length_is_refused() { - for ix in [init(), deposit(), submit_spend()] { + for ix in [init(), deposit(), submit_spend(), settle()] { let good = ix.pack(); for len in 0..good.len() { assert!( diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 096322a9..9e945c1d 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -49,6 +49,7 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> Pr relay_fee, }, ), + Instruction::SettleEpoch { count } => settle_epoch(program_id, accounts, count), } } @@ -341,14 +342,17 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest ) .map_err(|_| MirrorProgramError::NullifierAlreadySpent)?; + let now = solana_program::clock::Clock::get()?.unix_timestamp; let mut spend_data = spend_account.try_borrow_mut_data()?; let mut record = Spend::load_uninitialised(&mut spend_data)?; record.initialise( spend_bump, req.selector, req.relay_fee, + now, &req.beneficiary, &relay.key.to_bytes(), + &pool_account.key.to_bytes(), ); // The spend counter is deliberately *not* advanced here. The note is @@ -358,6 +362,152 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest Ok(()) } +/// How long a spend may wait before it can settle alone. +/// +/// Below the crowd size, a batch must wait this out. It is the escape valve that +/// makes the crowd requirement safe: without it, a quiet pool could hold a +/// member's funds indefinitely because the crowd never arrives, and a privacy +/// tool that can strand your money is not one anybody should use. +pub const SETTLE_TIMEOUT_SECONDS: i64 = 3_600; + +/// Executes a batch of pending spends in one transaction. +/// +/// Every payout in the batch shares a timestamp and an ordering, which is what +/// makes the crowd synchronised: an observer watching beneficiaries receive +/// funds cannot use arrival time to tell them apart. +/// +/// Permissionless. Anyone may settle, so no operator's absence can strand a +/// member — and a member can always settle their own batch once the timeout has +/// passed. +/// +/// The crowd rule: a batch must carry at least `k_floor` spends, **or** every +/// spend in it must have waited out `SETTLE_TIMEOUT_SECONDS`. Requiring the crowd +/// unconditionally would be a liveness hazard on a quiet pool; dropping the +/// requirement would make "synchronised" a word rather than a property. This is +/// the honest middle: synchronised when there is traffic, still liquid when +/// there is not. +fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> ProgramResult { + if count == 0 { + return Err(MirrorProgramError::MalformedInstruction.into()); + } + + let iter = &mut accounts.iter(); + let settler = next_account_info(iter)?; + let pool_account = next_account_info(iter)?; + let vault_account = next_account_info(iter)?; + + if !settler.is_signer { + return Err(MirrorProgramError::MissingSignature.into()); + } + if pool_account.owner != program_id || vault_account.owner != program_id { + return Err(MirrorProgramError::InvalidOwner.into()); + } + if *vault_account.key != vault_address(program_id, pool_account.key).0 { + return Err(MirrorProgramError::InvalidPda.into()); + } + + let (denomination, k_floor) = { + let mut data = pool_account.try_borrow_mut_data()?; + let pool = Pool::load(&mut data)?; + (pool.denomination(), pool.k_floor()) + }; + + let now = solana_program::clock::Clock::get()?.unix_timestamp; + let crowd_satisfied = count as u32 >= k_floor; + + // Each spend brings its record, its beneficiary and its relay. + let mut settled = 0u64; + for _ in 0..count { + let spend_account = next_account_info(iter)?; + let beneficiary = next_account_info(iter)?; + let relay = next_account_info(iter)?; + + if spend_account.owner != program_id { + return Err(MirrorProgramError::InvalidOwner.into()); + } + + let (relay_fee, payout) = { + let mut data = spend_account.try_borrow_mut_data()?; + let mut record = Spend::load(&mut data)?; + + // A record from another pool must not reach this vault. + if record.pool() != pool_account.key.to_bytes() { + return Err(MirrorProgramError::InvalidPda.into()); + } + // The accounts must be the ones the proof bound. + if record.beneficiary() != beneficiary.key.to_bytes() + || record.relay() != relay.key.to_bytes() + { + return Err(MirrorProgramError::InvalidPda.into()); + } + if !crowd_satisfied + && now.saturating_sub(record.submitted_at()) < SETTLE_TIMEOUT_SECONDS + { + return Err(MirrorProgramError::CrowdTooSmall.into()); + } + + // Refuses a second settlement of the same record, which is the + // double-spend an attacker would reach for: pass one pending spend + // twice in a single batch and be paid twice. + record.mark_settled()?; + + let relay_fee = record.relay_fee(); + let payout = denomination + .checked_sub(relay_fee) + .ok_or(MirrorProgramError::ArithmeticOverflow)?; + (relay_fee, payout) + }; + + // The vault is owned by this program, so its lamports move by direct + // mutation rather than a system-program CPI. That means no signer seeds + // and no nested invoke on the hot path. + move_lamports(vault_account, beneficiary, payout)?; + if relay_fee > 0 { + move_lamports(vault_account, relay, relay_fee)?; + } + settled += 1; + } + + let mut data = pool_account.try_borrow_mut_data()?; + let mut pool = Pool::load(&mut data)?; + for _ in 0..settled { + pool.record_spend()?; + } + + // The accounting invariant, re-read from the account after the lamports + // actually moved rather than assumed from the arithmetic above. + let rent = Rent::get()?; + let required = pool + .required_vault_lamports()? + .checked_add(rent.minimum_balance(0)) + .ok_or(MirrorProgramError::ArithmeticOverflow)?; + if vault_account.lamports() < required { + return Err(MirrorProgramError::InsolventVault.into()); + } + Ok(()) +} + +/// Moves lamports between two accounts this program owns or may credit. +fn move_lamports( + from: &AccountInfo, + to: &AccountInfo, + amount: u64, +) -> Result<(), MirrorProgramError> { + let mut from_lamports = from + .try_borrow_mut_lamports() + .map_err(|_| MirrorProgramError::InsolventVault)?; + let mut to_lamports = to + .try_borrow_mut_lamports() + .map_err(|_| MirrorProgramError::InsolventVault)?; + **from_lamports = from_lamports + .checked_sub(amount) + .ok_or(MirrorProgramError::InsolventVault)?; + **to_lamports = to_lamports + .checked_add(amount) + .ok_or(MirrorProgramError::ArithmeticOverflow)?; + Ok(()) +} + /// Appends `leaf` to the pool's frontier, returning its index. /// /// The same walk as `mirror_core::Frontier::insert`, over account bytes. The diff --git a/programs/mirror-pool/src/spend.rs b/programs/mirror-pool/src/spend.rs index 70756847..75ecb731 100644 --- a/programs/mirror-pool/src/spend.rs +++ b/programs/mirror-pool/src/spend.rs @@ -1,9 +1,8 @@ //! The spend record. //! //! One account per nullifier, seeded by it. Its *existence* is the replay -//! guard — creating it fails if it already exists, so a second spend of the same -//! note cannot even reach the verifier — and its *contents* are the action the -//! proof authorised, held until settlement. +//! guard — `submit_spend` refuses outright if it is already there — and its +//! contents are the action the proof authorised, held until settlement. //! //! Folding the nullifier marker and the pending action into one account is //! deliberate: two accounts keyed by the same value can disagree, and an account @@ -23,15 +22,17 @@ mod offset { pub const _RESERVED: usize = 3; pub const SELECTOR: usize = 8; pub const RELAY_FEE: usize = 16; - pub const BENEFICIARY: usize = 24; - pub const RELAY: usize = 56; - pub const END: usize = 88; + pub const SUBMITTED_AT: usize = 24; + pub const BENEFICIARY: usize = 32; + pub const RELAY: usize = 64; + pub const POOL: usize = 96; + pub const END: usize = 128; } pub const SPEND_LEN: usize = offset::END; pub const SPEND_VERSION: u8 = 1; -const _: () = assert!(SPEND_LEN == 88); +const _: () = assert!(SPEND_LEN == 128); pub struct Spend<'a> { data: &'a mut [u8], @@ -49,9 +50,6 @@ impl<'a> Spend<'a> { if data.len() != SPEND_LEN { return Err(MirrorProgramError::InvalidSpendAccount); } - // A non-zero version means this nullifier has already been recorded. - // Account creation would normally have failed first; this is the second - // line of the replay guard, not the first. if data[offset::VERSION] != 0 { return Err(MirrorProgramError::NullifierAlreadySpent); } @@ -64,8 +62,10 @@ impl<'a> Spend<'a> { bump: u8, selector: u64, relay_fee: u64, + submitted_at: i64, beneficiary: &[u8; 32], relay: &[u8; 32], + pool: &[u8; 32], ) { self.data[offset::VERSION] = SPEND_VERSION; self.data[offset::STATUS] = STATUS_PENDING; @@ -73,8 +73,11 @@ impl<'a> Spend<'a> { self.data[offset::SELECTOR..offset::SELECTOR + 8].copy_from_slice(&selector.to_le_bytes()); self.data[offset::RELAY_FEE..offset::RELAY_FEE + 8] .copy_from_slice(&relay_fee.to_le_bytes()); + self.data[offset::SUBMITTED_AT..offset::SUBMITTED_AT + 8] + .copy_from_slice(&submitted_at.to_le_bytes()); self.data[offset::BENEFICIARY..offset::BENEFICIARY + 32].copy_from_slice(beneficiary); self.data[offset::RELAY..offset::RELAY + 32].copy_from_slice(relay); + self.data[offset::POOL..offset::POOL + 32].copy_from_slice(pool); } pub fn status(&self) -> u8 { @@ -93,6 +96,13 @@ impl<'a> Spend<'a> { b.copy_from_slice(&self.data[offset::RELAY_FEE..offset::RELAY_FEE + 8]); u64::from_le_bytes(b) } + /// Unix time the spend was accepted. Settlement uses it to decide whether a + /// batch may go out below the crowd size. + pub fn submitted_at(&self) -> i64 { + let mut b = [0u8; 8]; + b.copy_from_slice(&self.data[offset::SUBMITTED_AT..offset::SUBMITTED_AT + 8]); + i64::from_le_bytes(b) + } pub fn beneficiary(&self) -> [u8; 32] { let mut b = [0u8; 32]; b.copy_from_slice(&self.data[offset::BENEFICIARY..offset::BENEFICIARY + 32]); @@ -103,6 +113,15 @@ impl<'a> Spend<'a> { b.copy_from_slice(&self.data[offset::RELAY..offset::RELAY + 32]); b } + /// The pool this spend belongs to. + /// + /// Settlement checks it, so a record from one denomination cannot be + /// presented to another pool's vault. + pub fn pool(&self) -> [u8; 32] { + let mut b = [0u8; 32]; + b.copy_from_slice(&self.data[offset::POOL..offset::POOL + 32]); + b + } /// Marks the spend executed, refusing to do so twice. pub fn mark_settled(&mut self) -> Result<(), MirrorProgramError> { @@ -122,31 +141,38 @@ mod tests { vec![0u8; SPEND_LEN] } + fn init(data: &mut [u8]) { + let mut s = Spend::load_uninitialised(data).unwrap(); + s.initialise( + 251, + 7, + 12_345, + 1_700_000_000, + &[9u8; 32], + &[4u8; 32], + &[5u8; 32], + ); + } + #[test] fn a_spend_reads_back_what_was_written() { let mut data = fresh(); - let beneficiary = [9u8; 32]; - let relay = [4u8; 32]; - { - let mut s = Spend::load_uninitialised(&mut data).unwrap(); - s.initialise(251, 7, 12_345, &beneficiary, &relay); - } + init(&mut data); let s = Spend::load(&mut data).unwrap(); assert_eq!(s.status(), STATUS_PENDING); assert_eq!(s.bump(), 251); assert_eq!(s.selector(), 7); assert_eq!(s.relay_fee(), 12_345); - assert_eq!(s.beneficiary(), beneficiary); - assert_eq!(s.relay(), relay); + assert_eq!(s.submitted_at(), 1_700_000_000); + assert_eq!(s.beneficiary(), [9u8; 32]); + assert_eq!(s.relay(), [4u8; 32]); + assert_eq!(s.pool(), [5u8; 32]); } #[test] fn an_existing_record_refuses_reinitialisation() { let mut data = fresh(); - { - let mut s = Spend::load_uninitialised(&mut data).unwrap(); - s.initialise(1, 1, 1, &[1u8; 32], &[2u8; 32]); - } + init(&mut data); assert!(matches!( Spend::load_uninitialised(&mut data), Err(MirrorProgramError::NullifierAlreadySpent) @@ -156,10 +182,7 @@ mod tests { #[test] fn a_spend_settles_exactly_once() { let mut data = fresh(); - { - let mut s = Spend::load_uninitialised(&mut data).unwrap(); - s.initialise(1, 1, 1, &[1u8; 32], &[2u8; 32]); - } + init(&mut data); let mut s = Spend::load(&mut data).unwrap(); s.mark_settled().unwrap(); assert_eq!(s.status(), STATUS_SETTLED); @@ -181,23 +204,25 @@ mod tests { // Distinct values in every field; if two overlapped, one would clobber // the other and this would fail. let mut data = fresh(); - let beneficiary = [0xAB; 32]; - let relay = [0xCD; 32]; { let mut s = Spend::load_uninitialised(&mut data).unwrap(); s.initialise( 0xEE, 0x1122_3344_5566_7788, 0x99AA_BBCC_DDEE_FF00, - &beneficiary, - &relay, + -0x0102_0304_0506_0708, + &[0xAB; 32], + &[0xCD; 32], + &[0xEF; 32], ); } let s = Spend::load(&mut data).unwrap(); assert_eq!(s.bump(), 0xEE); assert_eq!(s.selector(), 0x1122_3344_5566_7788); assert_eq!(s.relay_fee(), 0x99AA_BBCC_DDEE_FF00); - assert_eq!(s.beneficiary(), beneficiary); - assert_eq!(s.relay(), relay); + assert_eq!(s.submitted_at(), -0x0102_0304_0506_0708); + assert_eq!(s.beneficiary(), [0xAB; 32]); + assert_eq!(s.relay(), [0xCD; 32]); + assert_eq!(s.pool(), [0xEF; 32]); } } diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index 3c8f9869..85d81e5b 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -16,7 +16,7 @@ use mirror_core::{Field, MerkleTree, Note}; use mirror_pool_program::{ instruction::Instruction as MirrorIx, pda::{pool_address, spend_address, vault_address}, - spend::{Spend, SPEND_LEN, STATUS_PENDING}, + spend::{Spend, SPEND_LEN, STATUS_PENDING, STATUS_SETTLED}, Pool, POOL_LEN, }; use solana_keypair::Keypair; @@ -75,15 +75,28 @@ fn setup() -> Env { } impl Env { + /// Sends a transaction, treating runtime deduplication as a test bug. + /// + /// A resubmitted byte-identical transaction is rejected as AlreadyProcessed + /// *before the program runs*, so any negative test that reaches that state is + /// asserting nothing about this program. Two tests in this file passed that + /// way before this guard existed. Vary the fee payer to make a genuine + /// retry. fn send(&mut self, ix: Instruction, signer: &Keypair) -> Result<(), String> { let msg = Message::new(&[ix], Some(&signer.pubkey())); let tx = Transaction::new(&[signer], msg, self.svm.latest_blockhash()); self.svm.send_transaction(tx).map(|_| ()).map_err(|e| { - format!( + let rendered = format!( "{:?} | logs: {:?}", e.err, e.meta.logs.iter().rev().take(4).collect::>() - ) + ); + assert!( + !rendered.contains("AlreadyProcessed"), + "the runtime deduplicated this transaction, so the program never \ + ran and the test proves nothing. Vary the fee payer: {rendered}" + ); + rendered }) } @@ -468,3 +481,226 @@ fn a_forged_proof_is_rejected() { .expect_err("a corrupted proof was accepted"); println!("forged proof rejected: {err}"); } + +// --------------------------------------------------------------------------- +// Settlement +// --------------------------------------------------------------------------- + +/// Submits `n` spends and returns their (spend PDA, beneficiary, relay) triples. +fn submit_batch( + env: &mut Env, + tree: &MerkleTree, + notes: &[Note], + keys: &Keys, + n: usize, +) -> Vec<(Pubkey, Pubkey, Keypair)> { + let mut out = Vec::new(); + for i in 0..n { + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let proof = proof_for(keys, tree, notes, i, &beneficiary); + let nullifier = proof.public_inputs[1]; + let ix = spend_ix(env, &proof, nullifier, &beneficiary, &relay.pubkey()); + env.send(ix, &relay) + .unwrap_or_else(|e| panic!("submit {i} failed: {e}")); + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + out.push((spend_pda, beneficiary, relay)); + } + out +} + +fn settle_ix(env: &Env, batch: &[(Pubkey, Pubkey, Keypair)], settler: &Pubkey) -> Instruction { + let mut metas = vec![ + AccountMeta::new(*settler, true), + AccountMeta::new(env.pool, false), + AccountMeta::new(env.vault, false), + ]; + for (spend, beneficiary, relay) in batch { + metas.push(AccountMeta::new(*spend, false)); + metas.push(AccountMeta::new(*beneficiary, false)); + metas.push(AccountMeta::new(relay.pubkey(), false)); + } + Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SettleEpoch { + count: batch.len() as u8, + } + .pack(), + metas, + ) +} + +#[test] +fn a_full_crowd_settles_and_pays_every_beneficiary() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let batch = submit_batch(&mut env, &tree, ¬es, &keys, K_FLOOR as usize); + + let before: Vec = batch + .iter() + .map(|(_, b, _)| env.svm.get_account(b).map(|a| a.lamports).unwrap_or(0)) + .collect(); + let vault_before = env.vault_lamports(); + + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let ix = settle_ix(&env, &batch, &settler.pubkey()); + let cu = env.send_expect_cu(ix, &settler); + println!( + "settle_epoch of {} spends consumed {cu} compute units", + batch.len() + ); + + let payout = DENOMINATION - RELAY_FEE; + for (i, (spend, beneficiary, _relay)) in batch.iter().enumerate() { + let after = env.svm.get_account(beneficiary).unwrap().lamports; + assert_eq!( + after - before[i], + payout, + "beneficiary {i} received the wrong amount" + ); + let mut data = env.svm.get_account(spend).unwrap().data; + let record = Spend::load(&mut data).unwrap(); + assert_eq!(record.status(), STATUS_SETTLED); + } + + // The vault paid out exactly the batch, nothing more. + assert_eq!( + vault_before - env.vault_lamports(), + batch.len() as u64 * DENOMINATION + ); + + // And the invariant still holds against the notes that remain unspent. + let mut data = env.pool_state(); + let pool = Pool::load(&mut data).unwrap(); + assert_eq!(pool.spend_count(), batch.len() as u64); + assert_eq!(pool.outstanding_notes().unwrap(), 6 - batch.len() as u64); + assert!( + env.vault_lamports() >= pool.required_vault_lamports().unwrap(), + "vault {} cannot cover {} outstanding notes", + env.vault_lamports(), + pool.outstanding_notes().unwrap() + ); +} + +/// The double-spend an attacker reaches for first: present one pending spend +/// twice inside a single batch and be paid twice for one note. +#[test] +fn the_same_spend_cannot_be_settled_twice_within_one_batch() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let batch = submit_batch(&mut env, &tree, ¬es, &keys, K_FLOOR as usize); + + // Replace the last entry with a duplicate of the first. + let mut doubled: Vec<(Pubkey, Pubkey, Keypair)> = batch + .iter() + .map(|(s, b, r)| (*s, *b, r.insecure_clone())) + .collect(); + let last = doubled.len() - 1; + doubled[last] = (batch[0].0, batch[0].1, batch[0].2.insecure_clone()); + + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let ix = settle_ix(&env, &doubled, &settler.pubkey()); + let err = env + .send(ix, &settler) + .expect_err("a spend was settled twice in one batch"); + println!("in-batch double settle rejected: {err}"); + assert!( + err.contains("Custom(16)"), + "expected AlreadySettled (16), got: {err}" + ); +} + +#[test] +fn a_settled_spend_cannot_be_settled_again_later() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let batch = submit_batch(&mut env, &tree, ¬es, &keys, K_FLOOR as usize); + + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let ix = settle_ix(&env, &batch, &settler.pubkey()); + env.send(ix, &settler).expect("first settlement"); + + // A different settler, so the transaction is not byte-identical and the + // rejection has to come from the program. + let other = Keypair::new(); + env.svm.airdrop(&other.pubkey(), 10_000_000_000).unwrap(); + let again = settle_ix(&env, &batch, &other.pubkey()); + let err = env + .send(again, &other) + .expect_err("a settled batch was replayed"); + println!("settlement replay rejected: {err}"); + assert!(err.contains("Custom(16)"), "expected AlreadySettled: {err}"); +} + +#[test] +fn a_batch_below_the_crowd_size_must_wait() { + let (mut env, tree, notes, keys) = seeded_pool(6); + // One short of the floor, and freshly submitted. + let batch = submit_batch(&mut env, &tree, ¬es, &keys, K_FLOOR as usize - 1); + + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let ix = settle_ix(&env, &batch, &settler.pubkey()); + let err = env + .send(ix, &settler) + .expect_err("a batch below the crowd size settled immediately"); + println!("small batch rejected: {err}"); + assert!( + err.contains("Custom(21)"), + "expected CrowdTooSmall (21), got: {err}" + ); +} + +/// The escape valve. A quiet pool must not hold a member's funds forever, so +/// once the timeout has passed a lone spend settles on its own. +#[test] +fn a_lone_spend_settles_once_the_timeout_has_passed() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let batch = submit_batch(&mut env, &tree, ¬es, &keys, 1); + + let early = Keypair::new(); + let late = Keypair::new(); + env.svm.airdrop(&early.pubkey(), 10_000_000_000).unwrap(); + env.svm.airdrop(&late.pubkey(), 10_000_000_000).unwrap(); + + // Before the timeout: refused. + let ix = settle_ix(&env, &batch, &early.pubkey()); + assert!(env.send(ix, &early).is_err(), "settled too early"); + + // Advance the clock past the timeout. + let mut clock = env.svm.get_sysvar::(); + clock.unix_timestamp += mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS + 1; + env.svm.set_sysvar(&clock); + + let before = env + .svm + .get_account(&batch[0].1) + .map(|a| a.lamports) + .unwrap_or(0); + let ix = settle_ix(&env, &batch, &late.pubkey()); + env.send(ix, &late).expect("lone spend after timeout"); + let after = env.svm.get_account(&batch[0].1).unwrap().lamports; + assert_eq!(after - before, DENOMINATION - RELAY_FEE); +} + +#[test] +fn settlement_refuses_a_beneficiary_the_proof_did_not_bind() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let batch = submit_batch(&mut env, &tree, ¬es, &keys, K_FLOOR as usize); + + // Swap one beneficiary for an address the member never proved. + let mut tampered: Vec<(Pubkey, Pubkey, Keypair)> = batch + .iter() + .map(|(s, b, r)| (*s, *b, r.insecure_clone())) + .collect(); + tampered[1].1 = Pubkey::new_unique(); + + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let ix = settle_ix(&env, &tampered, &settler.pubkey()); + let err = env + .send(ix, &settler) + .expect_err("settlement paid an unbound beneficiary"); + println!("unbound beneficiary rejected: {err}"); +} From 9f8334b81b407481c457fdfb56878b91a6d73796 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 05:25:00 -0300 Subject: [PATCH 14/83] test(program): pin the permissionless exit, which needs no new instruction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A member who cannot get a relay to act signs as their own relay with a zero fee, and settlement is already permissionless, so they settle their own batch once the timeout passes. Nothing in the protocol can hold their escrow: no relay has to cooperate and no operator has to be alive. So there is no self_spend instruction. The property was already there; what was missing was proof of it. Adding an instruction would have been more code for a guarantee the protocol already made. The cost is the expected one — their own wallet signs, giving up the anonymity the relay path provides. It is an escape hatch rather than a mode of operation, and the test says so. --- programs/mirror-pool/tests/end_to_end.rs | 84 ++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index 85d81e5b..aefef269 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -704,3 +704,87 @@ fn settlement_refuses_a_beneficiary_the_proof_did_not_bind() { .expect_err("settlement paid an unbound beneficiary"); println!("unbound beneficiary rejected: {err}"); } + +/// The permissionless exit, demonstrated rather than added. +/// +/// No `self_spend` instruction exists, because none is needed: a member acts as +/// their own relay with a zero fee, and settlement is already permissionless, so +/// they can settle their own batch once the timeout passes. Nothing in the +/// protocol can hold their escrow — no relay has to cooperate and no operator +/// has to be alive. +/// +/// The cost is exactly the one you would expect: their own wallet signs, so this +/// path gives up the anonymity the relay path provides. It is an escape hatch, +/// not a mode of operation, and this test pins that it works rather than leaving +/// it as an assertion in a README. +#[test] +fn a_member_can_always_exit_without_any_relay() { + let (mut env, tree, notes, keys) = seeded_pool(6); + + // The member is their own relay and their own beneficiary, fee zero. + let member = Keypair::new(); + env.svm.airdrop(&member.pubkey(), 10_000_000_000).unwrap(); + let beneficiary = member.pubkey(); + + let merkle_proof = tree.proof(0).unwrap(); + let binding = mirror_core::action_binding(SELECTOR, &beneficiary.to_bytes(), 0).unwrap(); + let witness = Witness { + note: notes[0], + merkle_proof: &merkle_proof, + root: tree.root().unwrap(), + action_binding: binding, + }; + let mut rng = { + use ark_std::rand::SeedableRng; + ark_std::rand::rngs::StdRng::from_seed([42u8; 32]) + }; + let proof = prove(&keys, &witness, &mut rng).expect("proving"); + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + + let ix = Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR, + beneficiary: beneficiary.to_bytes(), + relay_fee: 0, + } + .pack(), + vec![ + AccountMeta::new(member.pubkey(), true), + AccountMeta::new(env.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + env.send(ix, &member) + .expect("member submitted their own spend"); + + // Alone, so the crowd rule sends them to the timeout. + let mut clock = env.svm.get_sysvar::(); + clock.unix_timestamp += mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS + 1; + env.svm.set_sysvar(&clock); + + let before = env.svm.get_account(&member.pubkey()).unwrap().lamports; + let batch = vec![(spend_pda, beneficiary, member.insecure_clone())]; + let settle = settle_ix(&env, &batch, &member.pubkey()); + env.send(settle, &member) + .expect("member settled their own spend"); + + let after = env.svm.get_account(&member.pubkey()).unwrap().lamports; + // The full denomination, minus whatever the transaction itself cost. + assert!( + after > before, + "the member ended up worse off: {before} -> {after}" + ); + assert!( + after - before >= DENOMINATION - 100_000, + "expected roughly the full denomination back, got {}", + after - before + ); +} From ecdff38aeebc6322ae65cbb65d9ffb25c75298d8 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 05:27:16 -0300 Subject: [PATCH 15/83] feat(provenance): anonymity metrics, verified against reference vectors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ten quantities rather than a scalar, because a single number averages away the tail and the tail is where the members with no anonymity are. The folklore formula is inverted and this module says so. 2^H(C) — entropy over the class-size distribution — is widely quoted as the effective anonymity set, but it is the leakage: it is maximised when every member stands alone, which is total deanonymisation. all_singletons_is_total_deanonymisation pins the contrast, computing both and showing the folklore quantity reporting "32" for a set of 32 people who each have none. The headline is the loss factor rho = 2^-H(C) rather than effective-k, because it is independent of k and therefore comparable across pools. Effective-k measured at small k systematically understates the steady-state loss and cannot be extrapolated upward, so a headline in those units is not portable. worst_case_is_informative encodes something the field gets wrong. Under any heavy-tailed provenance prior somebody is always alone — P(min = 1) is about 1.00 for every k up to 512 — so "worst case is 1" describes provenance in general, not the pool being measured. A published measurement of a live Solana pool reports it as a finding; here it is only informative when the smallest class exceeds one. Correct attribution, since the usual citation is wrong: H is Serjantov-Danezis (PET 2002, Def. 2), defined in bits over users. The exponentiated form as a member count is Andersson & Lundin (2008, Def. 1), who dispute the misattribution. Both reference vectors from the methodology are asserted to 1e-9, and the chain rule H(C) + H(X|C) = log2 K and the ordering invariant are property-tested, so an arithmetic slip cannot pass quietly. --- crates/mirror-provenance/src/lib.rs | 16 +- crates/mirror-provenance/src/metrics.rs | 352 ++++++++++++++++++++++++ 2 files changed, 365 insertions(+), 3 deletions(-) create mode 100644 crates/mirror-provenance/src/metrics.rs diff --git a/crates/mirror-provenance/src/lib.rs b/crates/mirror-provenance/src/lib.rs index 0b1aae57..ac32835b 100644 --- a/crates/mirror-provenance/src/lib.rs +++ b/crates/mirror-provenance/src/lib.rs @@ -1,4 +1,14 @@ -//! Funding-provenance measurement over real on-chain data: partition an anonymity -//! set by where each member's capital came from and report effective-k rather than -//! nominal k. This is a measurement crate; it makes no privacy claims by itself. +//! Funding-provenance measurement over real on-chain data. +//! +//! An anonymity set on a public ledger can be partitioned by where each member's +//! capital came from, and once it is, what survives is the size of the actor's +//! class rather than the advertised `k`. This crate measures that. +//! +//! It is a measurement crate. It makes no privacy claims by itself, and every +//! number it produces names the data it came from. Method, adversary model and +//! the limits of what the numbers prove are in `docs/PROVENANCE_METHOD.md`. #![forbid(unsafe_code)] + +pub mod metrics; + +pub use metrics::Anonymity; diff --git a/crates/mirror-provenance/src/metrics.rs b/crates/mirror-provenance/src/metrics.rs new file mode 100644 index 00000000..df66e58e --- /dev/null +++ b/crates/mirror-provenance/src/metrics.rs @@ -0,0 +1,352 @@ +//! Anonymity metrics over a provenance partition. +//! +//! An anonymity set of `K` members is partitioned into classes by where each +//! member's capital came from. An adversary who learns a member's class is left +//! guessing only within it, so what survives is not `K` but the residual +//! anonymity `H(X|C)`. +//! +//! The whole module rests on one identity, which `the_chain_rule_holds` pins: +//! +//! ```text +//! log2 K = H(C) + H(X|C) +//! ``` +//! +//! **The folklore formula is inverted.** `2^{H(C)}` — entropy over the +//! class-size *distribution* — is widely quoted as the effective anonymity set. +//! It is the leakage: it is maximised when every member is alone, which is total +//! deanonymisation. The anonymity is `2^{H(X|C)}`. Getting this backwards +//! produces a number that improves as privacy gets worse. +//! +//! ## Attribution +//! +//! `H` is the Serjantov–Danezis entropy metric (PET 2002, Definition 2), which +//! is defined in *bits* over *users*. The exponentiated form `2^H` as a member +//! count is Andersson & Lundin (IFIP AICT 262, 2008, Definition 1), who +//! explicitly dispute the common misattribution. We do not write "Serjantov and +//! Danezis define the effective anonymity set size as `2^H`", because they did +//! not. +//! +//! For each observation `C = c` the adversary's posterior is uniform on class +//! `c`, whose Serjantov–Danezis effective size is `log2 n_c` bits. `H(X|C)` is +//! therefore the expectation of that size over the adversary's observation. + +use std::collections::BTreeMap; + +/// Every quantity we report. Never a scalar — a single number hides the tail, +/// and the tail is where the people with no anonymity are. +#[derive(Debug, Clone, PartialEq)] +pub struct Anonymity { + /// Members in the set. + pub nominal_k: u64, + /// Distinct provenance classes. + pub classes: u64, + /// `ρ = 2^{−H(C)}`, the fraction of nominal k that survives. + /// + /// The headline, because it is independent of `k` and therefore comparable + /// across pools of different sizes. Effective-k measured at small `k` + /// systematically understates the steady-state loss and cannot be + /// extrapolated upward. + pub loss_factor: f64, + /// `2^{H(X|C)}`: the membership-weighted geometric mean of class sizes. + pub eff_k_shannon: f64, + /// `H(C)` in bits. Exactly the Shannon leakage `I(X;C)`. + pub leakage_shannon_bits: f64, + /// `K/m`: Bayes vulnerability, the unweighted arithmetic mean of class sizes. + pub eff_k_min_entropy: f64, + /// `log2 m` in bits. Exactly the min-entropy leakage. + pub leakage_min_entropy_bits: f64, + /// Smallest class. Meaningless without its null distribution — see + /// [`Anonymity::worst_case_is_informative`]. + pub worst_case: u64, + /// Expected number of guesses, `G(X|C)`. Uniform baseline is `(K+1)/2`. + pub guessing_entropy: f64, + /// Good–Turing coverage `1 − f₁/K`: the fraction of class mass observed. + pub good_turing_coverage: f64, + /// Chao1 richness estimate. A saturation diagnostic, not a class count. + pub chao1: f64, + /// `P(my class ≤ t)` at `t ∈ {1,2,4,8,16,32,64,128}`. + pub class_size_ccdf: Vec<(u64, f64)>, +} + +impl Anonymity { + /// Computes every metric from the class sizes of a partition. + /// + /// Returns `None` for an empty partition or one containing an empty class — + /// both mean the caller's partitioning is wrong, and silently coping would + /// hide that. + pub fn from_class_sizes(sizes: &[u64]) -> Option { + if sizes.is_empty() || sizes.contains(&0) { + return None; + } + let k: u64 = sizes.iter().sum(); + let m = sizes.len() as u64; + let kf = k as f64; + + // H(C) = -Σ p_c log2 p_c and H(X|C) = Σ p_c log2 n_c + let mut h_c = 0.0f64; + let mut h_x_given_c = 0.0f64; + for &n in sizes { + let p = n as f64 / kf; + h_c -= p * p.log2(); + h_x_given_c += p * (n as f64).log2(); + } + + let f1 = sizes.iter().filter(|&&n| n == 1).count() as f64; + let f2 = sizes.iter().filter(|&&n| n == 2).count() as f64; + + // G(X|C) = (1/K) Σ n_c(n_c+1)/2 + let guessing_entropy = sizes + .iter() + .map(|&n| { + let nf = n as f64; + nf * (nf + 1.0) / 2.0 + }) + .sum::() + / kf; + + let mut ccdf = Vec::new(); + for t in [1u64, 2, 4, 8, 16, 32, 64, 128] { + let members_in_small_classes: u64 = sizes.iter().filter(|&&n| n <= t).sum(); + ccdf.push((t, members_in_small_classes as f64 / kf)); + } + + Some(Anonymity { + nominal_k: k, + classes: m, + loss_factor: (-h_c).exp2(), + eff_k_shannon: h_x_given_c.exp2(), + leakage_shannon_bits: h_c, + eff_k_min_entropy: kf / m as f64, + leakage_min_entropy_bits: (m as f64).log2(), + worst_case: *sizes.iter().min().expect("non-empty"), + guessing_entropy, + good_turing_coverage: 1.0 - f1 / kf, + chao1: m as f64 + f1 * (f1 - 1.0) / (2.0 * (f2 + 1.0)), + class_size_ccdf: ccdf, + }) + } + + /// Groups members by class label and measures the resulting partition. + pub fn from_labels(labels: &[L]) -> Option { + let mut counts: BTreeMap = BTreeMap::new(); + for l in labels { + *counts.entry(l.clone()).or_insert(0) += 1; + } + let sizes: Vec = counts.into_values().collect(); + Self::from_class_sizes(&sizes) + } + + /// Whether reporting `worst_case` says anything about *this* pool. + /// + /// Under any heavy-tailed provenance prior somebody is always alone: in + /// simulation `P(min_c n_c = 1) ≈ 1.00` for every `k ≤ 512`. So "worst case + /// is 1" describes the shape of provenance in general, not a property of the + /// pool being measured, and quoting it as a finding — as a published + /// measurement of a live Solana pool does — is not informative. + /// + /// It becomes informative only when the smallest class is larger than one, + /// which is a genuine and unusual claim. + pub fn worst_case_is_informative(&self) -> bool { + self.worst_case > 1 + } + + /// The ordering that must hold for any partition. + /// + /// `min_c n_c ≤ K/m ≤ 2^{H(X|C)} ≤ K` + /// + /// Worst case is at most the arithmetic mean of class sizes, which is at + /// most the membership-weighted geometric mean, which is at most nominal. + /// A violation means an arithmetic error, so it is asserted rather than + /// assumed. + pub fn ordering_holds(&self) -> bool { + const EPS: f64 = 1e-9; + let worst = self.worst_case as f64; + let nominal = self.nominal_k as f64; + worst <= self.eff_k_min_entropy + EPS + && self.eff_k_min_entropy <= self.eff_k_shannon + EPS + && self.eff_k_shannon <= nominal + EPS + } + + /// `H(C) + H(X|C) == log2 K`, the identity the module rests on. + pub fn chain_rule_residual(&self) -> f64 { + let h_x_given_c = self.eff_k_shannon.log2(); + (self.leakage_shannon_bits + h_x_given_c) - (self.nominal_k as f64).log2() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn close(a: f64, b: f64, tol: f64) -> bool { + (a - b).abs() < tol + } + + /// Reference vector A from the methodology, computed independently at full + /// double precision. Asserted to 1e-9 so an arithmetic slip cannot hide. + #[test] + fn reference_vector_a() { + let a = Anonymity::from_class_sizes(&[50, 20, 10, 10, 5, 3, 1, 1]).unwrap(); + assert_eq!(a.nominal_k, 100); + assert_eq!(a.classes, 8); + assert!( + close(a.leakage_shannon_bits, 2.1295115772, 1e-9), + "H(C) = {}", + a.leakage_shannon_bits + ); + assert!( + close(a.eff_k_shannon, 22.8535219812, 1e-9), + "eff_k = {}", + a.eff_k_shannon + ); + assert!(close(a.eff_k_min_entropy, 12.5, 1e-9)); + assert!(close(a.leakage_min_entropy_bits, 3.0, 1e-9)); + assert_eq!(a.worst_case, 1); + assert!( + close(a.guessing_entropy, 16.18, 1e-9), + "G = {}", + a.guessing_entropy + ); + assert!(close(a.good_turing_coverage, 0.98, 1e-9)); + assert!(close(a.chao1, 9.0, 1e-9)); + } + + /// Reference vector B: the shape of the published live-pool measurement — + /// one class of 19 plus eleven singletons. + #[test] + fn reference_vector_b() { + let b = Anonymity::from_class_sizes(&[19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]).unwrap(); + assert_eq!(b.nominal_k, 30); + assert_eq!(b.classes, 12); + assert!(close(b.leakage_shannon_bits, 2.2165365038, 1e-9)); + assert!( + close(b.eff_k_shannon, 6.4547181110, 1e-9), + "eff_k = {}", + b.eff_k_shannon + ); + assert!(close(b.eff_k_min_entropy, 2.5, 1e-9)); + assert!(close(b.leakage_min_entropy_bits, 3.5849625007, 1e-9)); + assert!(close(b.guessing_entropy, 6.7, 1e-9)); + assert!(close(b.good_turing_coverage, 0.6333333333, 1e-9)); + } + + /// The identity the module rests on. If this fails, every number above is + /// meaningless. + #[test] + fn the_chain_rule_holds() { + for sizes in [ + vec![1u64], + vec![5, 5], + vec![50, 20, 10, 10, 5, 3, 1, 1], + vec![19, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + vec![1000, 1], + (1..=40u64).collect::>(), + ] { + let a = Anonymity::from_class_sizes(&sizes).unwrap(); + assert!( + a.chain_rule_residual().abs() < 1e-9, + "H(C) + H(X|C) != log2 K for {sizes:?}: residual {}", + a.chain_rule_residual() + ); + } + } + + #[test] + fn the_ordering_invariant_holds_over_many_partitions() { + // A deterministic sweep rather than a random one, so a failure is + // reproducible without recording a seed. + let mut checked = 0; + for m in 1..=12u64 { + for spread in 0..12u64 { + let sizes: Vec = (0..m).map(|i| 1 + i * spread).collect(); + let a = Anonymity::from_class_sizes(&sizes).unwrap(); + assert!(a.ordering_holds(), "ordering violated for {sizes:?}: {a:?}"); + assert!(a.chain_rule_residual().abs() < 1e-9); + checked += 1; + } + } + assert!(checked > 100); + } + + /// One class means no leakage and full anonymity. + #[test] + fn a_single_class_leaks_nothing() { + let a = Anonymity::from_class_sizes(&[64]).unwrap(); + assert!(close(a.leakage_shannon_bits, 0.0, 1e-12)); + assert!(close(a.loss_factor, 1.0, 1e-12)); + assert!(close(a.eff_k_shannon, 64.0, 1e-9)); + assert_eq!(a.worst_case, 64); + assert!(a.worst_case_is_informative()); + } + + /// All singletons is total deanonymisation, and the folklore formula would + /// report this as *maximum* anonymity. + #[test] + fn all_singletons_is_total_deanonymisation() { + let a = Anonymity::from_class_sizes(&[1; 32]).unwrap(); + assert!( + close(a.eff_k_shannon, 1.0, 1e-12), + "eff_k = {}", + a.eff_k_shannon + ); + assert!(close(a.eff_k_min_entropy, 1.0, 1e-12)); + assert!(close(a.loss_factor, 1.0 / 32.0, 1e-12)); + // The inverted folklore quantity, for contrast: 2^{H(C)} = 32 here, + // which would be reported as "effective k = 32" — the nominal size, at + // the exact moment every member stands alone. + assert!(close(a.leakage_shannon_bits.exp2(), 32.0, 1e-9)); + } + + #[test] + fn the_loss_factor_is_independent_of_k_for_a_fixed_prior() { + // Doubling every class doubles nominal k and doubles effective k, so the + // ratio is unchanged. That is why it is the headline. + let small = Anonymity::from_class_sizes(&[8, 4, 2, 1, 1]).unwrap(); + let large = Anonymity::from_class_sizes(&[16, 8, 4, 2, 2]).unwrap(); + assert!( + close(small.loss_factor, large.loss_factor, 1e-12), + "{} vs {}", + small.loss_factor, + large.loss_factor + ); + assert!(large.nominal_k == 2 * small.nominal_k); + } + + #[test] + fn worst_case_alone_is_not_a_finding() { + // Both have a singleton, but they are not equally private. Reporting + // "worst case 1" for either says the same thing about two very + // different pools. + let bad = Anonymity::from_class_sizes(&[1; 20]).unwrap(); + let good = Anonymity::from_class_sizes(&[999, 1]).unwrap(); + assert_eq!(bad.worst_case, good.worst_case); + assert!(!bad.worst_case_is_informative()); + assert!(!good.worst_case_is_informative()); + assert!(good.eff_k_shannon > 900.0); + assert!(bad.eff_k_shannon < 1.001); + } + + #[test] + fn the_ccdf_reports_the_tail_the_averages_hide() { + let a = Anonymity::from_class_sizes(&[100, 1, 1, 1, 1]).unwrap(); + // Four of 104 members are alone. + let at_one = a.class_size_ccdf.iter().find(|(t, _)| *t == 1).unwrap().1; + assert!(close(at_one, 4.0 / 104.0, 1e-12)); + } + + #[test] + fn an_empty_or_degenerate_partition_is_refused() { + assert!(Anonymity::from_class_sizes(&[]).is_none()); + assert!(Anonymity::from_class_sizes(&[3, 0, 2]).is_none()); + } + + #[test] + fn labels_group_into_the_same_partition_as_sizes() { + let by_label = + Anonymity::from_labels(&["binance", "binance", "binance", "coinbase", "rootless"]) + .unwrap(); + let by_size = Anonymity::from_class_sizes(&[3, 1, 1]).unwrap(); + assert!(close(by_label.eff_k_shannon, by_size.eff_k_shannon, 1e-12)); + assert_eq!(by_label.classes, 3); + } +} From 018eb629d2913f608e6e0b775f0d64b6f0b7ffc3 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 05:36:40 -0300 Subject: [PATCH 16/83] feat(provenance): edge extraction, failure taxonomy, and a fail-closed RPC client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three pieces of the collector, none of which touch the network to be tested. Edges come from balance deltas, not instruction parsing. Parsing system transfers misses every program that moves lamports by direct account mutation and emits no transfer instruction at all, plus createAccount, transferWithSeed, withdrawNonceAccount and closeAccount returns. Deltas see value movement however it was caused. The payer's fee is added back so paying for a transaction is not mistaken for sending value, misaligned balance arrays are refused rather than zipped short, and a credit with several debited accounts is marked ambiguous rather than attributed to a guess. The failure taxonomy exists so an infrastructure limit can never read as an absence of evidence. RPC failures are counted on their own line and excluded from the class distribution entirely; above a 1% failure rate the run refuses to print a headline rather than printing a warning above one, because a warning gets dropped when the number is quoted. A published tracer in this space uses unwrap_or(0) for signature counts, so a throttled call there makes an address look like it has no history — which inflates precisely the bucket that flatters the measurement. The RPC client has no unwrap_or anywhere, and it checks the endpoint before the run. Verified live both ways: the public mainnet endpoint reports first available block 0 and serves slot 50,000,000 from 2020, so it passes; a third-party "public" endpoint reports a first available block 434,542,394 and is refused with exit code 2. That one returns null rather than an error for pruned history, so without this check a collection against it would look healthy and report every old funding event as absent. --- Cargo.lock | 476 +++++++++++++++++++++++- Cargo.toml | 3 + crates/mirror-cli/src/main.rs | 38 ++ crates/mirror-provenance/Cargo.toml | 1 + crates/mirror-provenance/src/edge.rs | 249 +++++++++++++ crates/mirror-provenance/src/lib.rs | 6 + crates/mirror-provenance/src/outcome.rs | 279 ++++++++++++++ crates/mirror-provenance/src/rpc.rs | 455 ++++++++++++++++++++++ 8 files changed, 1505 insertions(+), 2 deletions(-) create mode 100644 crates/mirror-provenance/src/edge.rs create mode 100644 crates/mirror-provenance/src/outcome.rs create mode 100644 crates/mirror-provenance/src/rpc.rs diff --git a/Cargo.lock b/Cargo.lock index 897cc80b..acd72163 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aead" version = "0.5.2" @@ -128,7 +134,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -139,7 +145,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -847,6 +853,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -1025,6 +1040,17 @@ dependencies = [ "ctutils", ] +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "eager" version = "0.1.0" @@ -1206,12 +1232,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059c31d7d36c43fe39d89e55711858b4da8be7eb6dabac23c7289b1a19489406" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +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 = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "funty" version = "2.0.0" @@ -1364,12 +1409,115 @@ dependencies = [ "typenum", ] +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1533,6 +1681,12 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + [[package]] name = "litesvm" version = "0.15.0" @@ -1639,6 +1793,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mirror-circuit" version = "0.1.0" @@ -1708,6 +1872,7 @@ dependencies = [ "serde", "serde_json", "thiserror 2.0.19", + "ureq", ] [[package]] @@ -1930,6 +2095,15 @@ dependencies = [ "universal-hash", ] +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -2085,6 +2259,20 @@ dependencies = [ "subtle", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.28" @@ -2100,6 +2288,41 @@ dependencies = [ "semver", ] +[[package]] +name = "rustls" +version = "0.23.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c54fcab019b409d04215d3a17cb438fd7fbf192ee61461f20f4fe18704bc138" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -2257,6 +2480,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "smallvec" version = "1.15.2" @@ -3780,6 +4009,12 @@ dependencies = [ "der", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "strsim" version = "0.11.1" @@ -3825,6 +4060,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "tap" version = "1.0.1" @@ -3880,6 +4126,16 @@ dependencies = [ "num_cpus", ] +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinyvec" version = "1.12.0" @@ -3984,6 +4240,30 @@ dependencies = [ "void", ] +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "2.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d1a66277ed75f640d608235660df48c8e3c19f3b4edb6a263315626cc3c01d" +dependencies = [ + "base64", + "flate2", + "log", + "once_cell", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "url", + "webpki-roots 0.26.11", +] + [[package]] name = "uriparse" version = "0.6.4" @@ -3994,6 +4274,24 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[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" @@ -4027,6 +4325,24 @@ dependencies = [ "wit-bindgen", ] +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.9", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4081,6 +4397,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[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.61.2" @@ -4090,6 +4415,70 @@ dependencies = [ "windows-link", ] +[[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 = "1.0.4" @@ -4105,6 +4494,12 @@ version = "0.57.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + [[package]] name = "wyz" version = "0.5.1" @@ -4114,6 +4509,29 @@ dependencies = [ "tap", ] +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.55" @@ -4134,6 +4552,27 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", + "synstructure", +] + [[package]] name = "zeroize" version = "1.9.0" @@ -4154,6 +4593,39 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 31055b67..98843ddd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,9 @@ serde_json = "1" clap = { version = "4", features = ["derive"] } sha2 = "0.10" hex = "0.4" +# Blocking HTTP: collection is rate-limited and sequential, so an async +# runtime would add machinery without buying throughput. +ureq = { version = "2", features = ["json"] } # Test-only: runs the built .so in a real SVM. 0.15 targets Agave 4.1.1, which is # the toolchain the Makefile installs. litesvm = "0.15" diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index d46eb6cc..fe5e6556 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -39,6 +39,21 @@ enum Command { #[arg(long, default_value = "programs/mirror-pool/src/vk.rs")] out: PathBuf, }, + /// Checks that an RPC endpoint can actually serve the history a provenance + /// measurement depends on, and refuses it otherwise. + /// + /// Run this before trusting any number a collection produces. A truncated + /// endpoint does not error on old data — it returns nothing — so a collector + /// pointed at one reports every old funding event as absent and produces a + /// graph that looks like a finding. + CheckEndpoint { + #[arg(long, default_value = "https://api.mainnet-beta.solana.com")] + endpoint: String, + /// Requests per second. The public endpoint sustains a measured + /// 0.28-0.55, far below its documented allowance. + #[arg(long, default_value_t = 0.4)] + rps: f64, + }, /// Recomputes the verifying key from a seed and reports its digest. /// /// This is the check a third party runs. It binds the *whole* key — alpha, @@ -77,6 +92,29 @@ fn main() -> Result<()> { println!("vk sha256: {}", vk_digest(&svk)); Ok(()) } + Command::CheckEndpoint { endpoint, rps } => { + let mut client = mirror_provenance::RpcClient::new(&endpoint, rps); + match client.check_preconditions() { + Ok(check) => { + println!("endpoint {}", check.endpoint); + println!("first available block {}", check.first_available_block); + println!( + "archival probe slot {} ok", + mirror_provenance::rpc::ARCHIVAL_PROBE_SLOT + ); + println!("\nUSABLE — this endpoint serves history to genesis."); + Ok(()) + } + Err(e) => { + eprintln!("REFUSED: {e}"); + eprintln!( + "\nA measurement taken here would report old funding events as \ + absent and the resulting unresolved bucket would be an artifact." + ); + std::process::exit(2); + } + } + } Command::VerifySetup { seed, expect } => { let keys = mirror_circuit::generate_reproducible(seed.as_bytes()) .map_err(|e| anyhow::anyhow!("{e}"))?; diff --git a/crates/mirror-provenance/Cargo.toml b/crates/mirror-provenance/Cargo.toml index 8c738fd8..aadf7517 100644 --- a/crates/mirror-provenance/Cargo.toml +++ b/crates/mirror-provenance/Cargo.toml @@ -9,3 +9,4 @@ anyhow.workspace = true serde.workspace = true serde_json.workspace = true thiserror.workspace = true +ureq.workspace = true diff --git a/crates/mirror-provenance/src/edge.rs b/crates/mirror-provenance/src/edge.rs new file mode 100644 index 00000000..1252d986 --- /dev/null +++ b/crates/mirror-provenance/src/edge.rs @@ -0,0 +1,249 @@ +//! Funding-edge extraction from a transaction's balance deltas. +//! +//! We diff `preBalances` against `postBalances` rather than parsing +//! instructions. Parsing `program == "system" && type == "transfer"` misses +//! every program that moves lamports by direct account mutation and emits no +//! transfer instruction at all, plus `createAccount`, +//! `createAccountWithSeed`, `transferWithSeed`, `withdrawNonceAccount`, and +//! `closeAccount` lamport returns. A tracer built on instruction parsing is +//! blind to all of it and reports the resulting gaps as "unresolved". +//! +//! Balance deltas see value movement regardless of how it was caused, which is +//! the property we actually want. +//! +//! ```text +//! Δ_i = postBalances[i] − preBalances[i] + (fee if i == 0) +//! sources = { i : Δ_i < 0 } sinks = { i : Δ_i > 0 } +//! ``` +//! +//! The fee is added back for the payer so that paying for a transaction is not +//! mistaken for sending value. + +use serde::{Deserialize, Serialize}; + +/// A value movement observed in one transaction. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct FundingEdge { + /// The account credited. + pub sink: String, + /// Accounts debited. More than one means attribution within the transaction + /// is ambiguous. + pub sources: Vec, + /// Lamports credited to the sink. + pub value: u64, + pub signature: String, + pub slot: u64, + pub block_time: Option, + /// Set when several accounts were debited, so the true source of this + /// credit is one of a set rather than known. The rate of this is reported. + pub ambiguous_attribution: bool, +} + +/// The parts of a transaction we need. Deliberately a narrow projection: the +/// committed sample stores this rather than whole transactions, which keeps the +/// artifact small enough to publish and makes what we relied on explicit. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct TransactionView { + pub signature: String, + pub slot: u64, + pub block_time: Option, + /// The **fully resolved** key list. Under `jsonParsed`, lookup-table + /// addresses are folded into `accountKeys` and `preBalances`/`postBalances` + /// align with it, so no separate `loadedAddresses` handling is needed. This + /// is verified empirically; the RPC reference text implies otherwise. + pub account_keys: Vec, + pub pre_balances: Vec, + pub post_balances: Vec, + pub fee: u64, +} + +impl TransactionView { + /// Whether the balance arrays align with the resolved key list. + /// + /// A misalignment means we would attribute a delta to the wrong account, so + /// it is checked rather than assumed — this is exactly where a v0 + /// lookup-table transaction would silently poison the graph. + pub fn is_well_formed(&self) -> bool { + !self.account_keys.is_empty() + && self.pre_balances.len() == self.account_keys.len() + && self.post_balances.len() == self.account_keys.len() + } + + /// Signed lamport change per account, with the payer's fee added back. + pub fn deltas(&self) -> Option> { + if !self.is_well_formed() { + return None; + } + Some( + self.account_keys + .iter() + .enumerate() + .map(|(i, _)| { + let d = self.post_balances[i] as i128 - self.pre_balances[i] as i128; + if i == 0 { + d + self.fee as i128 + } else { + d + } + }) + .collect(), + ) + } + + /// The edge crediting `address`, if this transaction credits it. + pub fn edge_crediting(&self, address: &str) -> Option { + let deltas = self.deltas()?; + let index = self.account_keys.iter().position(|k| k == address)?; + let credited = deltas[index]; + if credited <= 0 { + return None; + } + + let sources: Vec = deltas + .iter() + .enumerate() + .filter(|(_, d)| **d < 0) + .map(|(i, _)| self.account_keys[i].clone()) + .collect(); + if sources.is_empty() { + return None; + } + + Some(FundingEdge { + sink: address.to_string(), + ambiguous_attribution: sources.len() > 1, + sources, + value: credited as u64, + signature: self.signature.clone(), + slot: self.slot, + block_time: self.block_time, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn tx(keys: &[&str], pre: &[u64], post: &[u64], fee: u64) -> TransactionView { + TransactionView { + signature: "sig".into(), + slot: 100, + block_time: Some(1_700_000_000), + account_keys: keys.iter().map(|s| s.to_string()).collect(), + pre_balances: pre.to_vec(), + post_balances: post.to_vec(), + fee, + } + } + + #[test] + fn a_simple_transfer_yields_one_source_and_one_sink() { + // Alice pays the fee and sends 1000 to Bob. + let t = tx(&["alice", "bob"], &[10_000, 0], &[8_995, 1_000], 5); + let edge = t.edge_crediting("bob").expect("bob was credited"); + assert_eq!(edge.value, 1_000); + assert_eq!(edge.sources, vec!["alice"]); + assert!(!edge.ambiguous_attribution); + } + + /// The fee must not make the payer look like a sender when it isn't one, + /// and must not hide a genuine send. + #[test] + fn the_payers_fee_is_added_back() { + // Alice pays a fee and moves nothing. Her delta is exactly zero, so she + // is neither a source nor a sink. + let t = tx(&["alice", "bob"], &[10_000, 500], &[9_995, 500], 5); + assert_eq!(t.deltas().unwrap()[0], 0); + assert!(t.edge_crediting("bob").is_none(), "bob gained nothing"); + } + + #[test] + fn a_program_moving_lamports_directly_is_still_seen() { + // No system-transfer instruction exists here — a program mutated + // lamports. Instruction parsing would find nothing; the delta is plain. + let t = tx( + &["payer", "vault", "recipient"], + &[1_000, 50_000, 0], + &[995, 40_000, 10_000], + 5, + ); + let edge = t.edge_crediting("recipient").unwrap(); + assert_eq!(edge.value, 10_000); + assert_eq!(edge.sources, vec!["vault"]); + } + + #[test] + fn several_debited_accounts_mark_the_attribution_ambiguous() { + let t = tx( + &["payer", "second", "recipient"], + &[10_000, 10_000, 0], + &[4_995, 5_000, 10_000], + 5, + ); + let edge = t.edge_crediting("recipient").unwrap(); + assert!( + edge.ambiguous_attribution, + "two sources means the true funder is one of a set, not known" + ); + assert_eq!(edge.sources.len(), 2); + } + + #[test] + fn an_account_that_lost_value_is_not_credited() { + let t = tx(&["alice", "bob"], &[10_000, 5_000], &[10_995, 4_000], 5); + assert!(t.edge_crediting("bob").is_none()); + assert!(t.edge_crediting("alice").is_some()); + } + + #[test] + fn an_address_absent_from_the_key_list_yields_nothing() { + let t = tx(&["alice", "bob"], &[10_000, 0], &[8_995, 1_000], 5); + assert!(t.edge_crediting("carol").is_none()); + } + + /// Misaligned balance arrays are refused rather than truncated. This is the + /// shape a mishandled v0 lookup-table transaction takes, and silently + /// zipping the shorter array would attribute deltas to the wrong accounts. + #[test] + fn misaligned_balances_are_refused() { + let mut t = tx(&["a", "b", "c"], &[1, 2, 3], &[1, 2, 3], 0); + t.post_balances.pop(); + assert!(!t.is_well_formed()); + assert!(t.deltas().is_none()); + assert!(t.edge_crediting("b").is_none()); + } + + #[test] + fn a_lookup_table_transaction_is_handled_when_keys_are_fully_resolved() { + // Twenty resolved keys, eleven from the transaction and nine from a + // lookup table, with balances aligned to the full list. + let keys: Vec = (0..20).map(|i| format!("key{i}")).collect(); + let mut pre = vec![1_000u64; 20]; + let mut post = vec![1_000u64; 20]; + pre[0] = 100_000; + post[0] = 89_995; + post[17] = 11_000; + let t = TransactionView { + signature: "v0sig".into(), + slot: 1, + block_time: None, + account_keys: keys, + pre_balances: pre, + post_balances: post, + fee: 5, + }; + assert!(t.is_well_formed()); + let edge = t + .edge_crediting("key17") + .expect("a lookup-table account was credited"); + assert_eq!(edge.value, 10_000); + assert_eq!(edge.sources, vec!["key0"]); + } + + #[test] + fn zero_value_credits_are_not_edges() { + let t = tx(&["a", "b"], &[10_000, 500], &[9_995, 500], 5); + assert!(t.edge_crediting("b").is_none()); + } +} diff --git a/crates/mirror-provenance/src/lib.rs b/crates/mirror-provenance/src/lib.rs index ac32835b..0dad7d98 100644 --- a/crates/mirror-provenance/src/lib.rs +++ b/crates/mirror-provenance/src/lib.rs @@ -9,6 +9,12 @@ //! the limits of what the numbers prove are in `docs/PROVENANCE_METHOD.md`. #![forbid(unsafe_code)] +pub mod edge; pub mod metrics; +pub mod outcome; +pub mod rpc; +pub use edge::{FundingEdge, TransactionView}; pub use metrics::Anonymity; +pub use outcome::{Census, Outcome, TerminalRule, Unresolved}; +pub use rpc::{RpcClient, RpcError}; diff --git a/crates/mirror-provenance/src/outcome.rs b/crates/mirror-provenance/src/outcome.rs new file mode 100644 index 00000000..bb6310d1 --- /dev/null +++ b/crates/mirror-provenance/src/outcome.rs @@ -0,0 +1,279 @@ +//! How a trace ends, and the census that keeps infrastructure limits from +//! reading as evidence. +//! +//! The governing principle: **an RPC failure is not a finding.** A rate-limited +//! call that returns nothing must never be recorded as "this address has no +//! funder", because that is indistinguishable in the output from a genuine +//! terminal — and it inflates exactly the bucket a privacy measurement most +//! wants to be large. +//! +//! A published tracer in this space uses `.unwrap_or(0)` for signature counts +//! and `.unwrap_or_default()` for signature lists. A throttled call there makes +//! an address look like it has zero history, so it is not detected as a hub and +//! is admitted to the sample, and a throttled trace yields no funder, so the +//! member is classified unresolved. Its large unresolved bucket is what +//! systematic rate limiting would manufacture, and its headline does not say so. + +use serde::{Deserialize, Serialize}; + +/// Which terminal rule classified an address. Recorded so every rule can be +/// ablated in the sensitivity table rather than taken on faith. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum TerminalRule { + /// R1: the account is a program or PDA — not owned by the system program, + /// or executable. Definitional, one call, and what separates "funded by a + /// Raydium vault" from "funded by a person". + ProgramOrPda, + /// R2: a hit in the curated, MIT-licensed anchor set. + CuratedAnchor, + /// R3: a member of a fee-payer or sweep cluster of at least three. + StructuralCluster, + /// R4: high lifetime activity and at least thirty days old. + /// + /// This is **not** an attributable origin and must never be described as + /// one. It means "busy address we could not name". + VolumeHub, + /// R5: has funded many distinct previously-unseen addresses. + Distributor, +} + +/// Why a trace failed to reach a class. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Unresolved { + /// The address has no qualifying incoming value edge. Genuine evidence. + NoIncomingEdge, + /// The incoming edge was below the minimum value threshold. Evidence. + BelowThreshold, + /// Hit the depth limit. A budget limit, not evidence. + DepthExceeded, + /// Hit the signature page cap without terminating. Budget, not evidence. + PageCapHit, + /// An RPC error, 429 exhaustion, or timeout. **Not evidence.** + RpcFailure, + /// SOL-only scope, and the address holds token accounts with inflow, so the + /// funding event is structurally invisible. A scope limit, not evidence. + SplBlindSpot, +} + +impl Unresolved { + /// Whether this outcome tells us something about the address, as opposed to + /// something about our own budget or infrastructure. + pub fn is_evidence(&self) -> bool { + matches!( + self, + Unresolved::NoIncomingEdge | Unresolved::BelowThreshold + ) + } + + /// Whether this outcome is a failure of ours rather than a property of the + /// chain. These members leave the class distribution entirely. + pub fn is_failure(&self) -> bool { + matches!(self, Unresolved::RpcFailure) + } +} + +/// The end state of tracing one member. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub enum Outcome { + /// Classified. The label is what the partition groups on. + Terminal { rule: TerminalRule, label: String }, + /// Not classified, with the reason kept. + Unresolved { reason: Unresolved, depth: u32 }, +} + +impl Outcome { + pub fn label(&self) -> Option<&str> { + match self { + Outcome::Terminal { label, .. } => Some(label), + Outcome::Unresolved { .. } => None, + } + } +} + +/// A count of every terminal state in a run. There is no "other" bucket, +/// because an "other" bucket is where an unaccounted failure would hide. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub struct Census { + pub resolved: u64, + pub no_incoming_edge: u64, + pub below_threshold: u64, + pub depth_exceeded: u64, + pub page_cap_hit: u64, + pub rpc_failure: u64, + pub spl_blind_spot: u64, +} + +/// Above this rate of RPC failures a run refuses to publish a headline. +/// +/// One percent. Beyond it, the unresolved bucket is substantially our own +/// making, and any effective-k computed from it would be measuring our +/// infrastructure rather than the pool. +pub const MAX_FAILURE_RATE: f64 = 0.01; + +impl Census { + pub fn record(&mut self, outcome: &Outcome) { + match outcome { + Outcome::Terminal { .. } => self.resolved += 1, + Outcome::Unresolved { reason, .. } => match reason { + Unresolved::NoIncomingEdge => self.no_incoming_edge += 1, + Unresolved::BelowThreshold => self.below_threshold += 1, + Unresolved::DepthExceeded => self.depth_exceeded += 1, + Unresolved::PageCapHit => self.page_cap_hit += 1, + Unresolved::RpcFailure => self.rpc_failure += 1, + Unresolved::SplBlindSpot => self.spl_blind_spot += 1, + }, + } + } + + /// Every member the run attempted, including failures. + pub fn attempted(&self) -> u64 { + self.resolved + + self.no_incoming_edge + + self.below_threshold + + self.depth_exceeded + + self.page_cap_hit + + self.rpc_failure + + self.spl_blind_spot + } + + /// Members whose outcome says something about the chain: everything except + /// our own RPC failures. + pub fn measurable(&self) -> u64 { + self.attempted() - self.rpc_failure + } + + pub fn failure_rate(&self) -> f64 { + let attempted = self.attempted(); + if attempted == 0 { + return 0.0; + } + self.rpc_failure as f64 / attempted as f64 + } + + /// Whether a headline may be published from this run. + /// + /// Deliberately a hard gate rather than a warning. A warning printed above a + /// number gets dropped when the number is quoted. + pub fn may_publish(&self) -> bool { + self.attempted() > 0 && self.failure_rate() <= MAX_FAILURE_RATE + } + + /// The line that must accompany any reported figure. + pub fn summary(&self) -> String { + format!( + "attempted {} | resolved {} | evidence-unresolved {} | budget-unresolved {} \ + | scope-unresolved {} | rpc failures {} ({:.2}%)", + self.attempted(), + self.resolved, + self.no_incoming_edge + self.below_threshold, + self.depth_exceeded + self.page_cap_hit, + self.spl_blind_spot, + self.rpc_failure, + self.failure_rate() * 100.0 + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn terminal(label: &str) -> Outcome { + Outcome::Terminal { + rule: TerminalRule::CuratedAnchor, + label: label.to_string(), + } + } + + fn unresolved(reason: Unresolved) -> Outcome { + Outcome::Unresolved { reason, depth: 3 } + } + + #[test] + fn an_rpc_failure_is_never_evidence() { + assert!(!Unresolved::RpcFailure.is_evidence()); + assert!(Unresolved::RpcFailure.is_failure()); + // The two that genuinely say something about the address. + assert!(Unresolved::NoIncomingEdge.is_evidence()); + assert!(Unresolved::BelowThreshold.is_evidence()); + // Budget and scope limits are neither. + for r in [ + Unresolved::DepthExceeded, + Unresolved::PageCapHit, + Unresolved::SplBlindSpot, + ] { + assert!(!r.is_evidence(), "{r:?} must not count as evidence"); + assert!(!r.is_failure(), "{r:?} is not an infrastructure failure"); + } + } + + #[test] + fn the_census_accounts_for_every_member() { + let mut c = Census::default(); + c.record(&terminal("entity:binance")); + c.record(&terminal("entity:binance")); + c.record(&unresolved(Unresolved::NoIncomingEdge)); + c.record(&unresolved(Unresolved::RpcFailure)); + c.record(&unresolved(Unresolved::DepthExceeded)); + c.record(&unresolved(Unresolved::SplBlindSpot)); + assert_eq!(c.attempted(), 6); + assert_eq!( + c.measurable(), + 5, + "failures leave the distribution entirely" + ); + assert_eq!(c.resolved, 2); + } + + #[test] + fn a_run_with_too_many_failures_refuses_to_publish() { + let mut c = Census::default(); + for _ in 0..99 { + c.record(&terminal("entity:x")); + } + c.record(&unresolved(Unresolved::RpcFailure)); + // 1 in 100 is exactly at the limit and still publishable. + assert!((c.failure_rate() - 0.01).abs() < 1e-12); + assert!(c.may_publish()); + + c.record(&unresolved(Unresolved::RpcFailure)); + assert!( + !c.may_publish(), + "two failures in 101 is above the limit and must block the headline" + ); + } + + #[test] + fn an_empty_run_cannot_publish() { + assert!(!Census::default().may_publish()); + } + + #[test] + fn the_summary_names_every_bucket_separately() { + let mut c = Census::default(); + c.record(&terminal("a")); + c.record(&unresolved(Unresolved::RpcFailure)); + let s = c.summary(); + for expected in ["attempted", "resolved", "rpc failures"] { + assert!(s.contains(expected), "summary missing {expected}: {s}"); + } + // The failure count must be visible as its own number, not folded into + // the unresolved total. + assert!(s.contains("rpc failures 1"), "{s}"); + } + + #[test] + fn a_volume_hub_is_not_an_attributable_origin() { + // Encoded as a test because the distinction is the whole critique of a + // competing measurement: there, "reaches an attributable origin" means + // "the address hit the RPC page cap". + let hub = Outcome::Terminal { + rule: TerminalRule::VolumeHub, + label: "busy-unlabelled:SomeAddress".to_string(), + }; + assert!( + hub.label().unwrap().starts_with("busy-unlabelled:"), + "a volume hub must be labelled as unnamed, never as an entity" + ); + } +} diff --git a/crates/mirror-provenance/src/rpc.rs b/crates/mirror-provenance/src/rpc.rs new file mode 100644 index 00000000..ed5948ee --- /dev/null +++ b/crates/mirror-provenance/src/rpc.rs @@ -0,0 +1,455 @@ +//! A Solana JSON-RPC client that fails loudly. +//! +//! Two properties matter here more than throughput. +//! +//! **Every error propagates.** There is no `unwrap_or(0)` and no +//! `unwrap_or_default()` anywhere in this module. A throttled call returns an +//! error that becomes `Unresolved::RpcFailure`, which the census counts on its +//! own line and excludes from the class distribution. The alternative — a +//! default value — turns a rate limit into the claim "this address has no +//! funding history", which is the single easiest way for a provenance +//! measurement to be wrong in the direction that flatters it. +//! +//! **The endpoint is checked before the run.** Third-party "public" endpoints +//! silently truncate history: one reports a first available block 2.5 days +//! behind the tip and returns `{"result": null}` — not an error — for a 2021 +//! transaction. A collector pointed at it records "not found" for every old +//! funding event and produces a truncated graph that looks exactly like a +//! finding. [`RpcClient::check_preconditions`] refuses to run against one. + +use crate::edge::TransactionView; +use serde::Deserialize; +use std::time::{Duration, Instant}; + +/// A slot old enough that only an archival endpoint can serve it. +/// +/// Slot 50,000,000 was produced on 2020-11-19. An endpoint that cannot return +/// it cannot answer questions about where anybody's money came from. +pub const ARCHIVAL_PROBE_SLOT: u64 = 50_000_000; + +#[derive(Debug, thiserror::Error)] +pub enum RpcError { + #[error("transport failure: {0}")] + Transport(String), + #[error("rpc returned an error: {0}")] + Rpc(String), + #[error("unexpected response shape: {0}")] + Shape(String), + #[error("endpoint is not archival: {0}")] + NotArchival(String), +} + +/// What the endpoint claimed about itself, recorded in the run manifest so a +/// reader can see which endpoint produced the numbers and that it passed. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EndpointCheck { + pub endpoint: String, + pub first_available_block: u64, + pub archival_probe_ok: bool, +} + +pub struct RpcClient { + endpoint: String, + agent: ureq::Agent, + /// Minimum spacing between calls. + /// + /// The public endpoint sustains a measured 0.28–0.55 requests per second, + /// far below its documented allowance; the documented figure is not + /// reproducible. Pacing is therefore set from measurement, and the measured + /// range is what gets reported rather than the advertised one. + min_interval: Duration, + last_call: Option, + calls: u64, +} + +#[derive(Deserialize)] +struct JsonRpcResponse { + result: Option, + error: Option, +} + +/// One entry from `getSignaturesForAddress`. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct SignatureInfo { + pub signature: String, + pub slot: u64, + pub block_time: Option, + pub err: bool, +} + +/// The fields of `getAccountInfo` the program/PDA rule needs. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct AccountOwner { + pub owner: String, + pub executable: bool, +} + +/// The system program. An account owned by anything else is a program account +/// or a PDA, never a person's wallet. +pub const SYSTEM_PROGRAM: &str = "11111111111111111111111111111111"; + +impl RpcClient { + pub fn new(endpoint: impl Into, requests_per_second: f64) -> Self { + let interval = if requests_per_second > 0.0 { + Duration::from_secs_f64(1.0 / requests_per_second) + } else { + Duration::ZERO + }; + RpcClient { + endpoint: endpoint.into(), + agent: ureq::AgentBuilder::new() + .timeout(Duration::from_secs(30)) + .build(), + min_interval: interval, + last_call: None, + calls: 0, + } + } + + pub fn calls_made(&self) -> u64 { + self.calls + } + + pub fn endpoint(&self) -> &str { + &self.endpoint + } + + fn pace(&mut self) { + if let Some(last) = self.last_call { + let elapsed = last.elapsed(); + if elapsed < self.min_interval { + std::thread::sleep(self.min_interval - elapsed); + } + } + self.last_call = Some(Instant::now()); + } + + fn call( + &mut self, + method: &str, + params: serde_json::Value, + ) -> Result { + self.pace(); + self.calls += 1; + let body = serde_json::json!({ + "jsonrpc": "2.0", + "id": self.calls, + "method": method, + "params": params, + }); + let response: JsonRpcResponse = self + .agent + .post(&self.endpoint) + .send_json(body) + .map_err(|e| RpcError::Transport(e.to_string()))? + .into_json() + .map_err(|e| RpcError::Shape(e.to_string()))?; + + if let Some(err) = response.error { + return Err(RpcError::Rpc(err.to_string())); + } + response + .result + .ok_or_else(|| RpcError::Shape(format!("{method}: neither result nor error"))) + } + + /// Refuses to proceed against an endpoint that cannot see the whole chain. + /// + /// Both probes are hard. A truncated endpoint does not error on old data, it + /// returns nothing, so without these the run would look healthy and be + /// systematically wrong. + pub fn check_preconditions(&mut self) -> Result { + let first = self.call("getFirstAvailableBlock", serde_json::json!([]))?; + let first_available_block = first + .as_u64() + .ok_or_else(|| RpcError::Shape("getFirstAvailableBlock: not a number".into()))?; + if first_available_block != 0 { + return Err(RpcError::NotArchival(format!( + "first available block is {first_available_block}, not 0; this endpoint \ + retains only recent history and would report old funding events as absent" + ))); + } + + let block = self.call( + "getBlock", + serde_json::json!([ + ARCHIVAL_PROBE_SLOT, + { "encoding": "json", "transactionDetails": "none", "rewards": false, + "maxSupportedTransactionVersion": 0 } + ]), + ); + let archival_probe_ok = matches!(&block, Ok(v) if !v.is_null()); + if !archival_probe_ok { + return Err(RpcError::NotArchival(format!( + "slot {ARCHIVAL_PROBE_SLOT} is unavailable; the endpoint cannot serve \ + the history this measurement depends on" + ))); + } + + Ok(EndpointCheck { + endpoint: self.endpoint.clone(), + first_available_block, + archival_probe_ok, + }) + } + + /// One page of signatures, newest first. `before` pages backwards. + /// + /// There is no forward cursor and `limit` caps at 1,000, so for an address + /// with fewer than 1,000 lifetime signatures the **last element of the first + /// page is the birth transaction**. That is what the birth-edge rule reads. + pub fn signatures_for_address( + &mut self, + address: &str, + before: Option<&str>, + limit: u32, + ) -> Result, RpcError> { + let mut config = serde_json::json!({ "limit": limit }); + if let Some(b) = before { + config["before"] = serde_json::Value::String(b.to_string()); + } + let result = self.call( + "getSignaturesForAddress", + serde_json::json!([address, config]), + )?; + let array = result + .as_array() + .ok_or_else(|| RpcError::Shape("getSignaturesForAddress: not an array".into()))?; + + array + .iter() + .map(|v| { + Ok(SignatureInfo { + signature: v + .get("signature") + .and_then(|s| s.as_str()) + .ok_or_else(|| RpcError::Shape("signature missing".into()))? + .to_string(), + slot: v + .get("slot") + .and_then(|s| s.as_u64()) + .ok_or_else(|| RpcError::Shape("slot missing".into()))?, + block_time: v.get("blockTime").and_then(|s| s.as_i64()), + err: v.get("err").map(|e| !e.is_null()).unwrap_or(false), + }) + }) + .collect() + } + + /// The narrow projection of a transaction the edge extractor needs. + /// + /// A `null` result is an error here, not an empty view. Against a truncated + /// endpoint `null` is what every old transaction returns, and treating it as + /// "no such transaction" is precisely the failure this module exists to + /// prevent. + pub fn transaction(&mut self, signature: &str) -> Result { + let result = self.call( + "getTransaction", + serde_json::json!([ + signature, + { "encoding": "jsonParsed", "maxSupportedTransactionVersion": 0 } + ]), + )?; + if result.is_null() { + return Err(RpcError::Rpc(format!( + "getTransaction({signature}) returned null; on an archival endpoint this \ + means the signature does not exist, but on a truncated one it means the \ + history was pruned — which is why preconditions are checked first" + ))); + } + parse_transaction(signature, &result) + } + + /// Owner and executability, for the program/PDA terminal rule. + pub fn account_owner(&mut self, address: &str) -> Result, RpcError> { + let result = self.call( + "getAccountInfo", + serde_json::json!([address, { "encoding": "base64" }]), + )?; + let value = result.get("value").unwrap_or(&serde_json::Value::Null); + if value.is_null() { + // A genuinely absent account. Distinct from a failure, and the + // caller treats it as such. + return Ok(None); + } + Ok(Some(AccountOwner { + owner: value + .get("owner") + .and_then(|o| o.as_str()) + .ok_or_else(|| RpcError::Shape("account owner missing".into()))? + .to_string(), + executable: value + .get("executable") + .and_then(|e| e.as_bool()) + .unwrap_or(false), + })) + } +} + +/// Projects a `getTransaction` response into the fields the edge extractor uses. +/// +/// Split out so it can be tested against a committed fixture without a network. +pub fn parse_transaction( + signature: &str, + value: &serde_json::Value, +) -> Result { + let meta = value + .get("meta") + .ok_or_else(|| RpcError::Shape("meta missing".into()))?; + + // Under `transactionDetails: "accounts"` the path is + // `transaction.accountKeys`; under `"full"` it is + // `transaction.message.accountKeys`. Accept either rather than depending on + // a request parameter staying in sync with a parser. + let keys_value = value + .pointer("/transaction/message/accountKeys") + .or_else(|| value.pointer("/transaction/accountKeys")) + .ok_or_else(|| RpcError::Shape("accountKeys missing".into()))?; + let account_keys: Vec = keys_value + .as_array() + .ok_or_else(|| RpcError::Shape("accountKeys is not an array".into()))? + .iter() + .map(|k| { + // jsonParsed renders each key as an object; base64/json as a string. + k.get("pubkey") + .and_then(|p| p.as_str()) + .or_else(|| k.as_str()) + .map(|s| s.to_string()) + .ok_or_else(|| RpcError::Shape("account key is neither object nor string".into())) + }) + .collect::>()?; + + let numbers = |field: &str| -> Result, RpcError> { + meta.get(field) + .and_then(|v| v.as_array()) + .ok_or_else(|| RpcError::Shape(format!("{field} missing")))? + .iter() + .map(|v| { + v.as_u64() + .ok_or_else(|| RpcError::Shape(format!("{field} entry is not a number"))) + }) + .collect() + }; + + Ok(TransactionView { + signature: signature.to_string(), + slot: value + .get("slot") + .and_then(|s| s.as_u64()) + .ok_or_else(|| RpcError::Shape("slot missing".into()))?, + block_time: value.get("blockTime").and_then(|t| t.as_i64()), + account_keys, + pre_balances: numbers("preBalances")?, + post_balances: numbers("postBalances")?, + fee: meta + .get("fee") + .and_then(|f| f.as_u64()) + .ok_or_else(|| RpcError::Shape("fee missing".into()))?, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A `jsonParsed` response whose keys are objects, which is the shape the + /// collector actually receives. + fn json_parsed_fixture() -> serde_json::Value { + serde_json::json!({ + "slot": 250_000_000u64, + "blockTime": 1_700_000_000i64, + "transaction": { + "message": { + "accountKeys": [ + { "pubkey": "Payer1111111111111111111111111111111111111", "signer": true, "writable": true, "source": "transaction" }, + { "pubkey": "Recip111111111111111111111111111111111111", "signer": false, "writable": true, "source": "transaction" }, + { "pubkey": "Lut11111111111111111111111111111111111111", "signer": false, "writable": true, "source": "lookupTable" } + ] + } + }, + "meta": { + "fee": 5000u64, + "preBalances": [1_000_000u64, 0u64, 7u64], + "postBalances": [ 895_000u64, 100_000u64, 7u64] + } + }) + } + + #[test] + fn a_json_parsed_transaction_projects_correctly() { + let v = json_parsed_fixture(); + let t = parse_transaction("sig123", &v).unwrap(); + assert_eq!(t.account_keys.len(), 3); + assert_eq!(t.pre_balances.len(), 3); + assert!(t.is_well_formed()); + assert_eq!(t.fee, 5000); + assert_eq!(t.slot, 250_000_000); + + let edge = t + .edge_crediting("Recip111111111111111111111111111111111111") + .expect("recipient credited"); + assert_eq!(edge.value, 100_000); + assert_eq!( + edge.sources, + vec!["Payer1111111111111111111111111111111111111"] + ); + } + + /// Lookup-table addresses arrive folded into `accountKeys`, and the balance + /// arrays align with the full resolved list. If that ever stopped being + /// true, deltas would be attributed to the wrong accounts. + #[test] + fn lookup_table_keys_are_included_and_balances_align() { + let t = parse_transaction("sig123", &json_parsed_fixture()).unwrap(); + assert!( + t.account_keys.iter().any(|k| k.starts_with("Lut1")), + "lookup-table key was dropped" + ); + assert_eq!(t.account_keys.len(), t.post_balances.len()); + } + + #[test] + fn the_flat_account_keys_shape_is_also_accepted() { + let v = serde_json::json!({ + "slot": 1u64, + "transaction": { "accountKeys": ["aaa", "bbb"] }, + "meta": { "fee": 0u64, "preBalances": [10u64, 0u64], "postBalances": [5u64, 5u64] } + }); + let t = parse_transaction("s", &v).unwrap(); + assert_eq!(t.account_keys, vec!["aaa", "bbb"]); + } + + #[test] + fn a_malformed_response_is_an_error_not_a_default() { + // Every one of these would become a silent zero under unwrap_or. + let no_meta = serde_json::json!({ "slot": 1u64, "transaction": { "accountKeys": ["a"] } }); + assert!(parse_transaction("s", &no_meta).is_err()); + + let no_balances = serde_json::json!({ + "slot": 1u64, + "transaction": { "accountKeys": ["a"] }, + "meta": { "fee": 0u64 } + }); + assert!(parse_transaction("s", &no_balances).is_err()); + + let no_slot = serde_json::json!({ + "transaction": { "accountKeys": ["a"] }, + "meta": { "fee": 0u64, "preBalances": [1u64], "postBalances": [1u64] } + }); + assert!(parse_transaction("s", &no_slot).is_err()); + } + + #[test] + fn the_system_program_constant_is_right() { + assert_eq!(SYSTEM_PROGRAM.len(), 32); + assert!(SYSTEM_PROGRAM.chars().all(|c| c == '1')); + } + + #[test] + fn pacing_is_derived_from_the_measured_rate() { + let slow = RpcClient::new("http://example.invalid", 0.4); + assert!(slow.min_interval >= Duration::from_millis(2_400)); + let fast = RpcClient::new("http://example.invalid", 10.0); + assert!(fast.min_interval <= Duration::from_millis(100)); + } +} From 09fdc51940dc21a394186ae89fd32940a0036dd1 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 05:48:17 -0300 Subject: [PATCH 17/83] feat(provenance): all five terminal rules, classified in a second pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collection and classification are separate passes, and that split is the point. Two of the five rules — cluster membership and fan-out — are properties of the whole sample rather than of an address, so classifying during traversal would make a member's class depend on the order it happened to be visited in. Collecting first and classifying over the complete set means the same sample always yields the same partition, which is what makes a published result checkable. classification_is_independent_of_collection_order pins it. The rules run R1, R2, R3, R5, R4 — R4 last, rather than fourth as the methodology table lists it, and for a stated reason. R4 is the only rule that names nothing: it yields busy-unlabelled, which is explicitly not an attributable origin. Any rule that can name an entity therefore gets the chance first, and R4 is the residual for "busy, and we could not say what". Evaluating it before R5 would relabel a demonstrable distributor as an anonymous hub and discard information already paid for in RPC calls. R1 is the rule no prior submission in this bounty performs, and it is one call: an account not owned by the system program is a program or a PDA, which is what separates "funded by a protocol vault" from "funded by a person". The hub threshold is decoupled from the paging cap, and a test asserts the gap. The competing tracer sets its threshold equal to the RPC page limit, so "is a hub" there means "hit the page cap" — which admits every DEX program and bot while excluding a genuine exchange withdrawal address with 800 transactions. Cluster ids are the lexicographically smallest member rather than the first one seen, so overlapping fee-payer groups resolve the same way every run. One real bug caught by its own test: age used saturating_sub on i64, which saturates at i64::MIN rather than at zero, so a first-seen timestamp in the future — clock skew or bad data — produced a large negative age that would have passed nothing and failed silently. --- crates/mirror-provenance/src/classify.rs | 498 +++++++++++++++++++++++ crates/mirror-provenance/src/facts.rs | 112 +++++ crates/mirror-provenance/src/lib.rs | 4 + 3 files changed, 614 insertions(+) create mode 100644 crates/mirror-provenance/src/classify.rs create mode 100644 crates/mirror-provenance/src/facts.rs diff --git a/crates/mirror-provenance/src/classify.rs b/crates/mirror-provenance/src/classify.rs new file mode 100644 index 00000000..9e8bf8e8 --- /dev/null +++ b/crates/mirror-provenance/src/classify.rs @@ -0,0 +1,498 @@ +//! The five terminal rules, applied over a complete fact set. +//! +//! Order is R1, R2, R3, R5, R4 — and R4 is deliberately last rather than fourth +//! as the methodology table lists it. R4 is the only rule that does not name +//! anything: it produces `busy-unlabelled`, which the methodology is explicit is +//! **not an attributable origin**. So any rule that can name an entity must get +//! the chance first, and R4 is the residual for "busy, and we could not say +//! what". Evaluating it before R5 would relabel a demonstrable distributor as an +//! anonymous hub, discarding information we already paid for. +//! +//! The rule that fired is carried in the outcome, so each can be ablated in a +//! sensitivity table rather than trusted as a block. + +use crate::{ + facts::AddressFacts, + outcome::{Outcome, TerminalRule}, + rpc::SYSTEM_PROGRAM, +}; +use std::collections::{BTreeMap, BTreeSet}; + +/// Thresholds, all configurable and all reported in the run manifest. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Thresholds { + /// Lifetime signatures at or above which an address is a volume hub. + /// + /// Deliberately far below the paging cap. The competing tracer sets its hub + /// threshold *equal* to the RPC page limit, so "is a hub" there means "hit + /// the page cap" — which admits every DEX program and bot while excluding a + /// genuine exchange withdrawal address with 800 transactions. + pub hub_signatures: u64, + /// Minimum account age for the hub rule, in seconds. A young address with + /// many transactions is a bot or an airdrop, not an origin. + pub hub_min_age_seconds: i64, + /// Distinct addresses an account must have created to be a distributor. + pub distributor_fanout: usize, + /// Members a fee-payer cluster needs before it counts as structural. + pub cluster_min_size: usize, +} + +impl Default for Thresholds { + fn default() -> Self { + Thresholds { + hub_signatures: 5_000, + hub_min_age_seconds: 30 * 24 * 3_600, + distributor_fanout: 20, + cluster_min_size: 3, + } + } +} + +/// The curated anchor set: addresses whose operator is known by name. +#[derive(Debug, Clone, Default)] +pub struct AnchorSet { + entries: BTreeMap, +} + +impl AnchorSet { + pub fn from_pairs, V: Into>( + pairs: impl IntoIterator, + ) -> Self { + AnchorSet { + entries: pairs + .into_iter() + .map(|(k, v)| (k.into(), v.into())) + .collect(), + } + } + + pub fn name_of(&self, address: &str) -> Option<&str> { + self.entries.get(address).map(|s| s.as_str()) + } + + pub fn len(&self) -> usize { + self.entries.len() + } + + pub fn is_empty(&self) -> bool { + self.entries.is_empty() + } +} + +/// Set-level structure derived once from the complete fact set. +/// +/// Built in one pass over everything collected, so membership never depends on +/// the order addresses were visited in. +#[derive(Debug, Clone, Default)] +pub struct SetStructure { + /// address -> canonical cluster id, for clusters at or above the threshold. + cluster_of: BTreeMap, +} + +impl SetStructure { + /// Groups addresses by shared fee payer and keeps groups large enough to be + /// structural. + /// + /// The cluster id is the lexicographically smallest member, so it does not + /// depend on iteration order or on which member was seen first. + pub fn build(facts: &BTreeMap, thresholds: &Thresholds) -> Self { + let mut by_payer: BTreeMap<&str, BTreeSet<&str>> = BTreeMap::new(); + for f in facts.values() { + for payer in &f.fee_payers { + by_payer + .entry(payer.as_str()) + .or_default() + .insert(f.address.as_str()); + } + } + + let mut cluster_of = BTreeMap::new(); + for members in by_payer.values() { + if members.len() < thresholds.cluster_min_size { + continue; + } + let canonical = members + .iter() + .min() + .expect("non-empty by construction") + .to_string(); + for m in members { + // A member already in a cluster keeps the smaller id, so + // overlapping payer groups resolve deterministically. + cluster_of + .entry(m.to_string()) + .and_modify(|existing: &mut String| { + if canonical < *existing { + *existing = canonical.clone(); + } + }) + .or_insert_with(|| canonical.clone()); + } + } + SetStructure { cluster_of } + } + + pub fn cluster_of(&self, address: &str) -> Option<&str> { + self.cluster_of.get(address).map(|s| s.as_str()) + } +} + +/// Applies the terminal rules to one address. +pub struct Classifier<'a> { + pub anchors: &'a AnchorSet, + pub structure: &'a SetStructure, + pub thresholds: &'a Thresholds, + /// Wall time the classification is relative to, for the age test. Passed in + /// rather than read from the clock so a replay reproduces exactly. + pub now: i64, +} + +impl Classifier<'_> { + /// Whether `facts` terminates a trace, and under which rule. + pub fn classify(&self, facts: &AddressFacts) -> Option { + // R1 — a program or PDA. Definitional and cheap, and the only thing that + // separates "funded by a protocol vault" from "funded by a person". No + // prior submission in this bounty performs it. + if let Some(owner) = &facts.owner { + if owner != SYSTEM_PROGRAM || facts.executable { + return Some(Outcome::Terminal { + rule: TerminalRule::ProgramOrPda, + label: format!("program:{}", facts.address), + }); + } + } + + // R2 — a named operator. + if let Some(name) = self.anchors.name_of(&facts.address) { + return Some(Outcome::Terminal { + rule: TerminalRule::CuratedAnchor, + label: format!("entity:{name}"), + }); + } + + // R3 — a fee-payer cluster large enough to be structural. + if let Some(id) = self.structure.cluster_of(&facts.address) { + return Some(Outcome::Terminal { + rule: TerminalRule::StructuralCluster, + label: format!("cluster:{id}"), + }); + } + + // R5 — created many distinct addresses. Evaluated before R4 because it + // names a role and R4 does not. + let distinct_funded: BTreeSet<&str> = facts.funded.iter().map(|s| s.as_str()).collect(); + if distinct_funded.len() >= self.thresholds.distributor_fanout { + return Some(Outcome::Terminal { + rule: TerminalRule::Distributor, + label: format!("distributor:{}", facts.address), + }); + } + + // R4 — busy and old, but unnamed. The label says so, and it must never + // be described as an attributable origin. + let old_enough = facts + .age_seconds(self.now) + .is_some_and(|age| age >= self.thresholds.hub_min_age_seconds); + if facts.signatures.lower_bound() >= self.thresholds.hub_signatures && old_enough { + return Some(Outcome::Terminal { + rule: TerminalRule::VolumeHub, + label: format!("busy-unlabelled:{}", facts.address), + }); + } + + None + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::facts::SigCount; + + const NOW: i64 = 1_800_000_000; + const OLD: i64 = NOW - 400 * 24 * 3_600; + const YOUNG: i64 = NOW - 3 * 24 * 3_600; + + fn wallet(address: &str) -> AddressFacts { + let mut f = AddressFacts::new(address); + f.owner = Some(SYSTEM_PROGRAM.to_string()); + f + } + + struct Env { + anchors: AnchorSet, + structure: SetStructure, + thresholds: Thresholds, + } + + impl Env { + fn new() -> Self { + Env { + anchors: AnchorSet::from_pairs([("BinanceHot1", "binance")]), + structure: SetStructure::default(), + thresholds: Thresholds::default(), + } + } + fn classify(&self, f: &AddressFacts) -> Option { + Classifier { + anchors: &self.anchors, + structure: &self.structure, + thresholds: &self.thresholds, + now: NOW, + } + .classify(f) + } + } + + #[test] + fn r1_catches_a_program() { + let env = Env::new(); + let mut f = wallet("SomeProgram"); + f.owner = Some("BPFLoaderUpgradeab1e11111111111111111111111".into()); + f.executable = true; + let out = env.classify(&f).unwrap(); + assert!(matches!( + out, + Outcome::Terminal { + rule: TerminalRule::ProgramOrPda, + .. + } + )); + assert_eq!(out.label().unwrap(), "program:SomeProgram"); + } + + #[test] + fn r1_catches_a_pda_that_is_not_executable() { + // A PDA owned by a program is not a person's wallet even though it runs + // no code. This is what distinguishes "funded by a vault" from "funded + // by someone". + let env = Env::new(); + let mut f = wallet("SomeVault"); + f.owner = Some("Whirlpoo1111111111111111111111111111111111".into()); + f.executable = false; + assert!(matches!( + env.classify(&f).unwrap(), + Outcome::Terminal { + rule: TerminalRule::ProgramOrPda, + .. + } + )); + } + + #[test] + fn a_plain_wallet_is_not_terminal_on_r1() { + let env = Env::new(); + assert!(env.classify(&wallet("JustAWallet")).is_none()); + } + + #[test] + fn an_account_that_does_not_exist_is_not_called_a_program() { + // owner is None: the account is absent, which we do not know how to + // classify. Treating absence as "not system-owned" would label every + // closed account a program. + let env = Env::new(); + let mut f = AddressFacts::new("Gone"); + f.owner = None; + assert!(env.classify(&f).is_none()); + } + + #[test] + fn r2_names_a_curated_anchor() { + let env = Env::new(); + let out = env.classify(&wallet("BinanceHot1")).unwrap(); + assert_eq!(out.label().unwrap(), "entity:binance"); + } + + #[test] + fn r3_groups_addresses_that_share_a_fee_payer() { + let mut facts = BTreeMap::new(); + for a in ["zebra", "alpha", "mango"] { + let mut f = wallet(a); + f.fee_payers = vec!["Sweeper1".into()]; + facts.insert(a.to_string(), f); + } + let thresholds = Thresholds::default(); + let structure = SetStructure::build(&facts, &thresholds); + + // The canonical id is the smallest member, so it does not depend on the + // order addresses were collected in. + assert_eq!(structure.cluster_of("zebra"), Some("alpha")); + assert_eq!(structure.cluster_of("mango"), Some("alpha")); + + let env = Env { + anchors: AnchorSet::default(), + structure, + thresholds, + }; + let out = env.classify(&facts["zebra"]).unwrap(); + assert_eq!(out.label().unwrap(), "cluster:alpha"); + } + + #[test] + fn a_fee_payer_group_below_the_threshold_is_not_a_cluster() { + let mut facts = BTreeMap::new(); + for a in ["one", "two"] { + let mut f = wallet(a); + f.fee_payers = vec!["Payer1".into()]; + facts.insert(a.to_string(), f); + } + let structure = SetStructure::build(&facts, &Thresholds::default()); + assert!( + structure.cluster_of("one").is_none(), + "two is below the floor of three" + ); + } + + #[test] + fn r5_catches_a_distributor_and_counts_distinct_addresses_only() { + let env = Env::new(); + let mut f = wallet("Fanout1"); + // Twenty entries but only five distinct: not a distributor. + f.funded = (0..20).map(|i| format!("child{}", i % 5)).collect(); + assert!(env.classify(&f).is_none(), "repeats must not count"); + + f.funded = (0..20).map(|i| format!("child{i}")).collect(); + let out = env.classify(&f).unwrap(); + assert!(matches!( + out, + Outcome::Terminal { + rule: TerminalRule::Distributor, + .. + } + )); + } + + #[test] + fn r4_needs_both_volume_and_age() { + let env = Env::new(); + let mut f = wallet("Busy1"); + f.signatures = SigCount::AtLeast(20_000); + + f.first_seen = Some(YOUNG); + assert!( + env.classify(&f).is_none(), + "a young address with many transactions is a bot, not an origin" + ); + + f.first_seen = Some(OLD); + let out = env.classify(&f).unwrap(); + assert!(matches!( + out, + Outcome::Terminal { + rule: TerminalRule::VolumeHub, + .. + } + )); + assert_eq!(out.label().unwrap(), "busy-unlabelled:Busy1"); + } + + /// The distinction the competing measurement collapses: a busy address is + /// not an attributable origin, and its label must never read like one. + #[test] + fn a_volume_hub_is_labelled_as_unnamed() { + let env = Env::new(); + let mut f = wallet("Busy2"); + f.signatures = SigCount::Exact(9_000); + f.first_seen = Some(OLD); + let label = env.classify(&f).unwrap().label().unwrap().to_string(); + assert!(label.starts_with("busy-unlabelled:")); + assert!(!label.starts_with("entity:")); + } + + /// The hub threshold must be reachable well inside the paging budget, or + /// "is a hub" degenerates into "we stopped looking". + #[test] + fn the_hub_threshold_is_decoupled_from_the_page_cap() { + let t = Thresholds::default(); + assert!( + t.hub_signatures < 20_000, + "the threshold must be reachable before paging stops, not equal to it" + ); + assert!( + t.hub_signatures > 1_000, + "and above a single page, or ordinary wallets qualify" + ); + } + + #[test] + fn a_named_entity_beats_an_anonymous_hub() { + // Both rules fire. The one that names something must win, or we discard + // information we already paid an RPC call for. + let env = Env::new(); + let mut f = wallet("BinanceHot1"); + f.signatures = SigCount::AtLeast(50_000); + f.first_seen = Some(OLD); + assert_eq!(env.classify(&f).unwrap().label().unwrap(), "entity:binance"); + } + + #[test] + fn a_distributor_beats_an_anonymous_hub() { + let env = Env::new(); + let mut f = wallet("Distrib1"); + f.signatures = SigCount::AtLeast(50_000); + f.first_seen = Some(OLD); + f.funded = (0..40).map(|i| format!("child{i}")).collect(); + assert!(matches!( + env.classify(&f).unwrap(), + Outcome::Terminal { + rule: TerminalRule::Distributor, + .. + } + )); + } + + #[test] + fn rule_precedence_is_total_and_stable() { + // Every rule fires at once; R1 must win, and the result must not depend + // on anything but the rule order. + let mut facts = BTreeMap::new(); + for a in ["aaa", "bbb", "ccc"] { + let mut f = wallet(a); + f.fee_payers = vec!["Payer1".into()]; + facts.insert(a.to_string(), f); + } + let thresholds = Thresholds::default(); + let structure = SetStructure::build(&facts, &thresholds); + + let mut everything = wallet("aaa"); + everything.owner = Some("SomeProgram11111111111111111111111111111111".into()); + everything.fee_payers = vec!["Payer1".into()]; + everything.funded = (0..50).map(|i| format!("c{i}")).collect(); + everything.signatures = SigCount::AtLeast(99_999); + everything.first_seen = Some(OLD); + + let env = Env { + anchors: AnchorSet::from_pairs([("aaa", "someexchange")]), + structure, + thresholds, + }; + assert!(matches!( + env.classify(&everything).unwrap(), + Outcome::Terminal { + rule: TerminalRule::ProgramOrPda, + .. + } + )); + } + + #[test] + fn classification_is_independent_of_collection_order() { + // The same addresses inserted in two different orders must produce the + // same clusters and therefore the same labels. This is the property the + // two-pass split exists to guarantee. + let build = |order: &[&str]| { + let mut facts = BTreeMap::new(); + for a in order { + let mut f = wallet(a); + f.fee_payers = vec!["P".into()]; + facts.insert(a.to_string(), f); + } + SetStructure::build(&facts, &Thresholds::default()) + }; + let a = build(&["delta", "alpha", "charlie"]); + let b = build(&["charlie", "delta", "alpha"]); + for addr in ["alpha", "charlie", "delta"] { + assert_eq!(a.cluster_of(addr), b.cluster_of(addr)); + } + } +} diff --git a/crates/mirror-provenance/src/facts.rs b/crates/mirror-provenance/src/facts.rs new file mode 100644 index 00000000..5343ae8e --- /dev/null +++ b/crates/mirror-provenance/src/facts.rs @@ -0,0 +1,112 @@ +//! What collection observed about an address. +//! +//! Pass one fills these in from the chain; pass two classifies over the complete +//! set. The split exists for determinism: two of the five terminal rules depend +//! on the *whole* sample — cluster membership and fan-out are properties of a +//! set, not of an address — so classifying during traversal would make the +//! result depend on visit order. Collect, then classify, and the same sample +//! always yields the same partition. + +use crate::edge::FundingEdge; +use serde::{Deserialize, Serialize}; + +/// A lifetime signature count, which is often only a lower bound. +/// +/// Paging stops at a cap. When it is hit, the true count is unknown and larger +/// than what we saw, so the distinction is kept in the type rather than +/// flattened into a number that would later be read as exact. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum SigCount { + Exact(u64), + /// Paging hit the cap. The real count is at least this. + AtLeast(u64), +} + +impl SigCount { + /// The count as a lower bound, which is all either variant guarantees. + pub fn lower_bound(&self) -> u64 { + match self { + SigCount::Exact(n) | SigCount::AtLeast(n) => *n, + } + } + + pub fn is_exact(&self) -> bool { + matches!(self, SigCount::Exact(_)) + } +} + +/// Everything pass one recorded about one address. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct AddressFacts { + pub address: String, + /// Account owner. `None` means the account does not exist on chain, which + /// is different from an RPC failure and is recorded as such. + pub owner: Option, + pub executable: bool, + pub signatures: SigCount, + /// Block time of the oldest signature seen, for the age test. + pub first_seen: Option, + /// The oldest incoming value credit: who created this account. + pub birth_edge: Option, + /// Fee payers observed across this address's transactions, for clustering. + pub fee_payers: Vec, + /// Addresses this one was the sole source of a birth edge for. Fan-out. + pub funded: Vec, +} + +impl AddressFacts { + pub fn new(address: impl Into) -> Self { + AddressFacts { + address: address.into(), + owner: None, + executable: false, + signatures: SigCount::Exact(0), + first_seen: None, + birth_edge: None, + fee_payers: Vec::new(), + funded: Vec::new(), + } + } + + /// Age in seconds at `now`, from the oldest signature we saw. + /// + /// Clamped at zero. `saturating_sub` on a signed integer saturates at + /// `i64::MIN`, not at nothing, so a timestamp in the future — clock skew, or + /// bad data — would otherwise yield a large negative age. Zero is the + /// fail-safe reading: it means "brand new", which fails the hub age test + /// rather than passing it by accident. + pub fn age_seconds(&self, now: i64) -> Option { + self.first_seen.map(|t| (now - t).max(0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn a_capped_count_is_a_lower_bound_and_says_so() { + let capped = SigCount::AtLeast(20_000); + assert_eq!(capped.lower_bound(), 20_000); + assert!(!capped.is_exact()); + + let exact = SigCount::Exact(37); + assert_eq!(exact.lower_bound(), 37); + assert!(exact.is_exact()); + } + + #[test] + fn age_is_measured_from_the_oldest_signature() { + let mut f = AddressFacts::new("addr"); + assert_eq!(f.age_seconds(1_000), None); + f.first_seen = Some(400); + assert_eq!(f.age_seconds(1_000), Some(600)); + } + + #[test] + fn a_future_timestamp_does_not_produce_a_negative_age() { + let mut f = AddressFacts::new("addr"); + f.first_seen = Some(2_000); + assert_eq!(f.age_seconds(1_000), Some(0)); + } +} diff --git a/crates/mirror-provenance/src/lib.rs b/crates/mirror-provenance/src/lib.rs index 0dad7d98..f1ab7e39 100644 --- a/crates/mirror-provenance/src/lib.rs +++ b/crates/mirror-provenance/src/lib.rs @@ -9,12 +9,16 @@ //! the limits of what the numbers prove are in `docs/PROVENANCE_METHOD.md`. #![forbid(unsafe_code)] +pub mod classify; pub mod edge; +pub mod facts; pub mod metrics; pub mod outcome; pub mod rpc; +pub use classify::{AnchorSet, Classifier, SetStructure, Thresholds}; pub use edge::{FundingEdge, TransactionView}; +pub use facts::{AddressFacts, SigCount}; pub use metrics::Anonymity; pub use outcome::{Census, Outcome, TerminalRule, Unresolved}; pub use rpc::{RpcClient, RpcError}; From fe91079f3a9fe9dafb57531fa4bb8a58c083ce58 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 06:21:19 -0300 Subject: [PATCH 18/83] feat(provenance): birth-edge traversal, sample cache, and the collect/analyze CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pass one walks each member's funding chain and writes everything it observed to a sample file; pass two classifies that file offline. The sample is the artifact the methodology says to commit, so collection and reproducibility come from one mechanism rather than two: anyone holding it recomputes the headline without RPC access and without trusting that our endpoint behaved the same way on their machine. Frame validation drops seeds that are not wallets — programs, PDAs, token accounts, addresses whose account no longer exists. That is a definitional criterion and the excluded count is published, because excluding addresses for looking hard to trace would be a different thing entirely and is exactly the bias that inflates a competing measurement's untraceable bucket. Two defects found by running it against live mainnet rather than by reasoning about it. The first was mine and it was silent. Frame validation fetches each seed's owner before tracing, and the observation cache treated "we know the owner" as "we have observed this address", so the trace skipped signature paging entirely and never looked for a birth edge. Every member came back unresolved. It was visible only because the run reported 14 RPC calls for 12 seeds and the census reported zero resolved — a collector that defaulted a skipped lookup to "no history" would have produced a plausible headline with nothing to indicate anything was wrong. The second is a finding rather than a bug, and it is recorded in docs/MEASUREMENT_LOG.md. Sampling addresses because they appear credited in a recent block is transaction-weighted, not member-weighted: an address that transacts a thousand times a day is a thousand times likelier to appear in any block, so the frame is size-biased toward precisely the addresses whose funding history is most expensive to walk. Eleven of twelve seeds turned out to be market makers and bots at the 20,000-signature page cap. The right frame for a pool is its depositors, each counted once. The tool printed no headline from that run. Reporting nothing was the correct output, and the measurement log records the run anyway — a measurement project that only keeps its successful runs is selecting rather than reporting. Also adds docs/ARCHITECTURE.md. --- crates/mirror-cli/src/main.rs | 162 ++++++ crates/mirror-provenance/src/lib.rs | 2 + crates/mirror-provenance/src/trace.rs | 703 ++++++++++++++++++++++++++ data/sample-smoke.json | 400 +++++++++++++++ data/seeds-smoke.txt | 12 + docs/ARCHITECTURE.md | 220 ++++++++ docs/MEASUREMENT_LOG.md | 76 +++ 7 files changed, 1575 insertions(+) create mode 100644 crates/mirror-provenance/src/trace.rs create mode 100644 data/sample-smoke.json create mode 100644 data/seeds-smoke.txt create mode 100644 docs/ARCHITECTURE.md create mode 100644 docs/MEASUREMENT_LOG.md diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index fe5e6556..eb6eec05 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -54,6 +54,32 @@ enum Command { #[arg(long, default_value_t = 0.4)] rps: f64, }, + /// Pass one: walks each seed's funding chain and writes a sample file. + /// + /// The only step that touches the network. Everything it observes goes into + /// the sample, so the number can be recomputed later by anyone holding it. + Collect { + /// File with one seed address per line. + #[arg(long)] + seeds: PathBuf, + #[arg(long, default_value = "sample.json")] + out: PathBuf, + #[arg(long, default_value = "https://api.mainnet-beta.solana.com")] + endpoint: String, + #[arg(long, default_value_t = 0.4)] + rps: f64, + #[arg(long, default_value_t = 6)] + depth: u32, + }, + /// Pass two: classifies a committed sample and reports the anonymity ladder. + /// + /// Pure and offline. Given the same sample it always produces the same + /// numbers, which is what makes a published result checkable by someone who + /// does not trust our endpoint or our run. + Analyze { + #[arg(long, default_value = "sample.json")] + sample: PathBuf, + }, /// Recomputes the verifying key from a seed and reports its digest. /// /// This is the check a third party runs. It binds the *whole* key — alpha, @@ -115,6 +141,142 @@ fn main() -> Result<()> { } } } + Command::Collect { + seeds, + out, + endpoint, + rps, + depth, + } => { + let text = std::fs::read_to_string(&seeds) + .with_context(|| format!("reading {}", seeds.display()))?; + let seed_list: Vec = text + .lines() + .map(|l| l.trim().to_string()) + .filter(|l| !l.is_empty() && !l.starts_with('#')) + .collect(); + anyhow::ensure!(!seed_list.is_empty(), "no seeds in {}", seeds.display()); + + let mut client = mirror_provenance::RpcClient::new(&endpoint, rps); + let check = client + .check_preconditions() + .map_err(|e| anyhow::anyhow!("endpoint refused: {e}"))?; + eprintln!( + "endpoint ok (first available block {}), collecting {} seeds", + check.first_available_block, + seed_list.len() + ); + + let config = mirror_provenance::CollectionConfig { + depth_max: depth, + ..Default::default() + }; + let thresholds = mirror_provenance::Thresholds::default(); + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH)? + .as_secs() as i64; + + let collector = mirror_provenance::Collector::new(&mut client, config); + let sample = collector.collect(&seed_list, &check, &thresholds, now); + + std::fs::write(&out, sample.to_json()?) + .with_context(|| format!("writing {}", out.display()))?; + println!( + "wrote {} ({} rpc calls)", + out.display(), + sample.manifest.rpc_calls + ); + if !sample.manifest.excluded_non_wallet.is_empty() { + println!( + "excluded {} seeds that are not wallets (program, PDA, token account, \ + or closed): a definitional frame criterion, not a difficulty one", + sample.manifest.excluded_non_wallet.len() + ); + } + println!( + "ambiguous attribution rate: {:.1}%", + sample.manifest.ambiguous_attribution_rate * 100.0 + ); + Ok(()) + } + Command::Analyze { sample } => { + let text = std::fs::read_to_string(&sample) + .with_context(|| format!("reading {}", sample.display()))?; + let sample = mirror_provenance::Sample::from_json(&text)?; + let thresholds = mirror_provenance::Thresholds::default(); + let anchors = mirror_provenance::AnchorSet::default(); + + let (results, census) = mirror_provenance::classify_sample( + &sample, + &anchors, + &thresholds, + sample.manifest.collected_at, + ); + + println!("endpoint {}", sample.manifest.endpoint); + println!("scope {:?}", sample.manifest.config.scope); + println!("census {}", census.summary()); + println!( + "frame {} seeds excluded as non-wallets before tracing", + sample.manifest.excluded_non_wallet.len() + ); + println!(); + + if !census.may_publish() { + eprintln!( + "REFUSING to report a headline: the RPC failure rate is {:.2}%, above the \ + 1% limit. Above that threshold the unresolved bucket is substantially our \ + own making and any effective-k computed from it would be measuring our \ + infrastructure rather than the pool.", + census.failure_rate() * 100.0 + ); + std::process::exit(3); + } + + let labels: Vec = results + .iter() + .filter_map(|(_, o)| o.label().map(|s| s.to_string())) + .collect(); + match mirror_provenance::Anonymity::from_labels(&labels) { + None => { + println!("no member resolved to a class; nothing to report"); + } + Some(a) => { + println!("resolved members {}", a.nominal_k); + println!("provenance classes {}", a.classes); + println!(); + println!( + "loss factor rho {:.4} <- headline, independent of k", + a.loss_factor + ); + println!("effective-k Shannon {:.4}", a.eff_k_shannon); + println!("effective-k min-ent {:.4}", a.eff_k_min_entropy); + println!("leakage Shannon {:.4} bits", a.leakage_shannon_bits); + println!( + "leakage min-entropy {:.4} bits", + a.leakage_min_entropy_bits + ); + println!("guessing entropy {:.2}", a.guessing_entropy); + println!("Good-Turing coverage {:.4}", a.good_turing_coverage); + println!("Chao1 richness {:.2}", a.chao1); + println!( + "worst-case class {}{}", + a.worst_case, + if a.worst_case_is_informative() { + "" + } else { + " (not informative: under any heavy-tailed prior somebody is always alone)" + } + ); + println!(); + println!("class-size CCDF (share of members in a class of at most t):"); + for (t, share) in &a.class_size_ccdf { + println!(" t={t:<4} {:.4}", share); + } + } + } + Ok(()) + } Command::VerifySetup { seed, expect } => { let keys = mirror_circuit::generate_reproducible(seed.as_bytes()) .map_err(|e| anyhow::anyhow!("{e}"))?; diff --git a/crates/mirror-provenance/src/lib.rs b/crates/mirror-provenance/src/lib.rs index f1ab7e39..86644f9a 100644 --- a/crates/mirror-provenance/src/lib.rs +++ b/crates/mirror-provenance/src/lib.rs @@ -15,6 +15,7 @@ pub mod facts; pub mod metrics; pub mod outcome; pub mod rpc; +pub mod trace; pub use classify::{AnchorSet, Classifier, SetStructure, Thresholds}; pub use edge::{FundingEdge, TransactionView}; @@ -22,3 +23,4 @@ pub use facts::{AddressFacts, SigCount}; pub use metrics::Anonymity; pub use outcome::{Census, Outcome, TerminalRule, Unresolved}; pub use rpc::{RpcClient, RpcError}; +pub use trace::{classify_sample, Chain, CollectionConfig, Collector, Sample, Scope}; diff --git a/crates/mirror-provenance/src/trace.rs b/crates/mirror-provenance/src/trace.rs new file mode 100644 index 00000000..4078e129 --- /dev/null +++ b/crates/mirror-provenance/src/trace.rs @@ -0,0 +1,703 @@ +//! Pass one: walk the birth-edge chain and record what was seen. +//! +//! The only networked step. Everything it observes is written to a sample file, +//! and pass two runs over that file with no network at all — so a published +//! number can be recomputed by anyone holding the sample, without RPC access and +//! without trusting that our endpoint behaved the same way on their machine. +//! +//! ## Why the oldest edge +//! +//! Funding is, by definition, among an address's *oldest* transactions. +//! `getSignaturesForAddress` returns newest-first with no forward cursor, so for +//! an address with fewer than one page of lifetime signatures the birth +//! transaction is the **last element of the first page**. +//! +//! A competing tracer scans the six most **recent** transactions of each +//! address. For anything with more than six transactions that is the wrong end +//! of the history, and it manufactures "unresolved" for exactly the active +//! wallets a funding trace most wants to follow. +//! +//! ## Where pass one stops +//! +//! Only on rules that are properties of the address itself: it is a program or a +//! PDA, it has no incoming edge, the edge is too small, or the budget ran out. +//! The set-level rules wait for pass two, which sees the whole sample. Stopping +//! early on an address-local rule is safe because pass two would fire the same +//! rule at the same place; the recorded chain is always a superset of what pass +//! two needs. + +use crate::{ + classify::Thresholds, + facts::{AddressFacts, SigCount}, + outcome::Unresolved, + rpc::{EndpointCheck, RpcClient, RpcError, SYSTEM_PROGRAM}, +}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +/// Which value flows the run followed. +/// +/// Recorded in the manifest because it bounds what the result can mean: a wallet +/// funded in USDC has no SOL funding event to find, and `getSignaturesForAddress` +/// does not index the recipient's wallet for a token transfer into an existing +/// account. A SOL-only run is structurally blind to the dominant exchange +/// withdrawal path, and its unresolved bucket is partly that blindness rather +/// than a property of the pool. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum Scope { + /// Native SOL only. + Sol, + /// SOL plus SPL token flows. Two and a half to three times the call count. + SolAndSpl, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CollectionConfig { + pub scope: Scope, + /// Hops to follow before giving up. + pub depth_max: u32, + /// Credits below this are not treated as funding. + pub min_edge_lamports: u64, + /// Signature pages to read before declaring the address high-activity. + pub sig_page_cap: u32, + pub page_size: u32, +} + +impl Default for CollectionConfig { + fn default() -> Self { + CollectionConfig { + scope: Scope::Sol, + depth_max: 6, + // Below the rent-exempt minimum for an empty account, so account + // creation itself always counts as funding. + min_edge_lamports: 500_000, + sig_page_cap: 20, + page_size: 1_000, + } + } +} + +/// Why pass one stopped walking a chain. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +pub enum ChainStop { + /// An address-local terminal rule fired. Pass two decides which. + LocalTerminal, + NoIncomingEdge, + BelowThreshold, + DepthExceeded, + PageCapHit, + RpcFailure, +} + +impl ChainStop { + /// The unresolved reason this stop maps to when no rule fires in pass two. + pub fn as_unresolved(&self) -> Unresolved { + match self { + // A local terminal that pass two does not confirm means the address + // stopped being classifiable, which is a budget outcome rather than + // evidence about the chain. + ChainStop::LocalTerminal | ChainStop::DepthExceeded => Unresolved::DepthExceeded, + ChainStop::NoIncomingEdge => Unresolved::NoIncomingEdge, + ChainStop::BelowThreshold => Unresolved::BelowThreshold, + ChainStop::PageCapHit => Unresolved::PageCapHit, + ChainStop::RpcFailure => Unresolved::RpcFailure, + } + } +} + +/// One member's provenance chain, seed first. +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct Chain { + pub seed: String, + pub visited: Vec, + pub stop: ChainStop, +} + +/// What the manifest records so a reader can judge the run without rerunning it. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Manifest { + pub endpoint: String, + pub first_available_block: u64, + pub archival_probe_ok: bool, + pub collected_at: i64, + pub config: CollectionConfig, + pub thresholds_hub_signatures: u64, + pub thresholds_distributor_fanout: usize, + pub thresholds_cluster_min_size: usize, + pub rpc_calls: u64, + /// Seeds dropped before tracing because they are not wallets: a program, a + /// PDA, a token account, or an address with no account at all. + /// + /// This is a **definitional** frame criterion, not an outcome-correlated + /// one. A token account is not a person and cannot be a member of an + /// anonymity set, so including it would measure something other than the + /// pool. Excluding addresses because they looked *hard to trace* would be a + /// different thing entirely and is exactly the bias that inflates a + /// competing measurement's untraceable bucket — so the count is published + /// rather than quietly applied. + pub excluded_non_wallet: Vec, + /// Share of observed credits whose source was one of several debited + /// accounts, so the funder is a set rather than a single address. + pub ambiguous_attribution_rate: f64, +} + +/// Everything pass one produced. This is the artifact that gets committed, and +/// pass two needs nothing else. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct Sample { + pub manifest: Manifest, + pub chains: Vec, + pub facts: BTreeMap, +} + +impl Sample { + pub fn to_json(&self) -> Result { + serde_json::to_string_pretty(self) + } + + pub fn from_json(s: &str) -> Result { + serde_json::from_str(s) + } +} + +/// Walks chains from each seed, recording facts along the way. +pub struct Collector<'a> { + client: &'a mut RpcClient, + config: CollectionConfig, + facts: BTreeMap, + /// Addresses whose observation is *complete*, meaning signatures were paged + /// and the birth edge resolved. + /// + /// Distinct from "we know the owner". Frame validation learns the owner of + /// every seed before tracing, so keying the cache on the owner alone would + /// make the trace believe every seed was already done and skip the paging + /// entirely — which yields a run where nothing resolves and nothing errors. + fully_observed: std::collections::BTreeSet, + ambiguous: u64, + edges_seen: u64, +} + +impl<'a> Collector<'a> { + pub fn new(client: &'a mut RpcClient, config: CollectionConfig) -> Self { + Collector { + client, + config, + facts: BTreeMap::new(), + fully_observed: std::collections::BTreeSet::new(), + ambiguous: 0, + edges_seen: 0, + } + } + + /// Collects every seed and assembles the sample. + pub fn collect( + mut self, + seeds: &[String], + check: &EndpointCheck, + thresholds: &Thresholds, + now: i64, + ) -> Sample { + let mut chains = Vec::with_capacity(seeds.len()); + let mut excluded_non_wallet = Vec::new(); + for seed in seeds { + match self.is_wallet(seed) { + Ok(true) => chains.push(self.trace(seed)), + Ok(false) => excluded_non_wallet.push(seed.clone()), + // An endpoint failure here is not evidence that the seed is not + // a wallet, so it stays in the frame and fails honestly during + // the trace. + Err(_) => chains.push(self.trace(seed)), + } + } + Sample { + manifest: Manifest { + endpoint: check.endpoint.clone(), + first_available_block: check.first_available_block, + archival_probe_ok: check.archival_probe_ok, + collected_at: now, + config: self.config.clone(), + thresholds_hub_signatures: thresholds.hub_signatures, + thresholds_distributor_fanout: thresholds.distributor_fanout, + thresholds_cluster_min_size: thresholds.cluster_min_size, + rpc_calls: self.client.calls_made(), + excluded_non_wallet, + ambiguous_attribution_rate: if self.edges_seen == 0 { + 0.0 + } else { + self.ambiguous as f64 / self.edges_seen as f64 + }, + }, + chains, + facts: self.facts, + } + } + + /// Whether a seed belongs in the frame at all. + /// + /// A member of an anonymity set is a person's wallet: an existing account + /// owned by the system program. A token account, a PDA, a program, or an + /// address whose account has been closed is none of those. + fn is_wallet(&mut self, address: &str) -> Result { + let owner = self.client.account_owner(address)?; + let verdict = match &owner { + None => false, + Some(o) => o.owner == SYSTEM_PROGRAM && !o.executable, + }; + // Keep what we learned; the trace would otherwise fetch it again. + let entry = self + .facts + .entry(address.to_string()) + .or_insert_with(|| AddressFacts::new(address)); + entry.executable = owner.as_ref().is_some_and(|o| o.executable); + entry.owner = owner.map(|o| o.owner); + Ok(verdict) + } + + fn trace(&mut self, seed: &str) -> Chain { + let mut visited = Vec::new(); + let mut current = seed.to_string(); + + for hop in 0..=self.config.depth_max { + visited.push(current.clone()); + + let stop = match self.observe(¤t) { + Err(_) => Some(ChainStop::RpcFailure), + Ok(local_terminal) if local_terminal => Some(ChainStop::LocalTerminal), + Ok(_) => None, + }; + if let Some(stop) = stop { + return Chain { + seed: seed.to_string(), + visited, + stop, + }; + } + + if hop == self.config.depth_max { + return Chain { + seed: seed.to_string(), + visited, + stop: ChainStop::DepthExceeded, + }; + } + + let facts = &self.facts[¤t]; + let Some(edge) = facts.birth_edge.clone() else { + let stop = if matches!(facts.signatures, SigCount::AtLeast(_)) { + ChainStop::PageCapHit + } else { + ChainStop::NoIncomingEdge + }; + return Chain { + seed: seed.to_string(), + visited, + stop, + }; + }; + if edge.value < self.config.min_edge_lamports { + return Chain { + seed: seed.to_string(), + visited, + stop: ChainStop::BelowThreshold, + }; + } + + // Follow the first source. With one source this is exact; with + // several the edge is already flagged ambiguous and the rate is + // reported, rather than the choice being hidden. + let next = edge.sources[0].clone(); + self.note_funding(&next, ¤t); + current = next; + } + + Chain { + seed: seed.to_string(), + visited, + stop: ChainStop::DepthExceeded, + } + } + + /// Records that `funder` created `child`, for the fan-out rule. + fn note_funding(&mut self, funder: &str, child: &str) { + let entry = self + .facts + .entry(funder.to_string()) + .or_insert_with(|| AddressFacts::new(funder)); + if !entry.funded.iter().any(|f| f == child) { + entry.funded.push(child.to_string()); + } + } + + /// Fills in one address's facts. Returns whether an address-local terminal + /// rule fired, which is the only reason pass one stops early. + fn observe(&mut self, address: &str) -> Result { + if self.fully_observed.contains(address) { + // Seen completely on another chain. Re-fetching would cost calls and + // could return a different answer as the chain advances, which would + // make the sample internally inconsistent. + let existing = &self.facts[address]; + return Ok(is_local_terminal(existing)); + } + + let mut facts = self + .facts + .remove(address) + .unwrap_or_else(|| AddressFacts::new(address)); + + // Frame validation may already have fetched this; do not pay twice. + if facts.owner.is_none() && !facts.executable { + let owner = self.client.account_owner(address)?; + facts.executable = owner.as_ref().is_some_and(|o| o.executable); + facts.owner = owner.map(|o| o.owner); + } + + if is_local_terminal(&facts) { + // A program or PDA terminates here; its funding history is not a + // person's and costs calls to walk. + self.facts.insert(address.to_string(), facts); + self.fully_observed.insert(address.to_string()); + return Ok(true); + } + + self.page_to_birth(address, &mut facts)?; + self.facts.insert(address.to_string(), facts); + self.fully_observed.insert(address.to_string()); + Ok(false) + } + + /// Pages backwards to the oldest signature and reads the birth edge from it. + fn page_to_birth(&mut self, address: &str, facts: &mut AddressFacts) -> Result<(), RpcError> { + let mut before: Option = None; + let mut seen: u64 = 0; + let mut oldest: Option = None; + + for page in 0..self.config.sig_page_cap { + let batch = self.client.signatures_for_address( + address, + before.as_deref(), + self.config.page_size, + )?; + if batch.is_empty() { + break; + } + seen += batch.len() as u64; + let last = batch.last().expect("non-empty").clone(); + before = Some(last.signature.clone()); + oldest = Some(last); + + if (batch.len() as u32) < self.config.page_size { + // A short page is the end of the history: this is exact. + facts.signatures = SigCount::Exact(seen); + break; + } + if page + 1 == self.config.sig_page_cap { + // The cap is a classification signal with a name, never a + // silent unresolved: the count is a lower bound and says so. + facts.signatures = SigCount::AtLeast(seen); + return Ok(()); + } + } + + let Some(oldest) = oldest else { + facts.signatures = SigCount::Exact(0); + return Ok(()); + }; + if !facts.signatures.is_exact() && seen > 0 { + facts.signatures = SigCount::Exact(seen); + } + facts.first_seen = oldest.block_time; + + let tx = self.client.transaction(&oldest.signature)?; + // The fee payer is the first key, and it is what fee-payer clustering + // groups on. + if let Some(payer) = tx.account_keys.first() { + if !facts.fee_payers.iter().any(|p| p == payer) { + facts.fee_payers.push(payer.clone()); + } + } + if let Some(edge) = tx.edge_crediting(address) { + self.edges_seen += 1; + if edge.ambiguous_attribution { + self.ambiguous += 1; + } + facts.birth_edge = Some(edge); + } + Ok(()) + } +} + +/// Address-local terminal test: a program or a PDA. +fn is_local_terminal(facts: &AddressFacts) -> bool { + facts + .owner + .as_ref() + .is_some_and(|o| o != SYSTEM_PROGRAM || facts.executable) +} + +/// Pass two: the first terminal along each chain, over the complete sample. +pub fn classify_sample( + sample: &Sample, + anchors: &crate::classify::AnchorSet, + thresholds: &Thresholds, + now: i64, +) -> ( + Vec<(String, crate::outcome::Outcome)>, + crate::outcome::Census, +) { + use crate::{ + classify::{Classifier, SetStructure}, + outcome::{Census, Outcome}, + }; + + let structure = SetStructure::build(&sample.facts, thresholds); + let classifier = Classifier { + anchors, + structure: &structure, + thresholds, + now, + }; + + let mut results = Vec::with_capacity(sample.chains.len()); + let mut census = Census::default(); + + for chain in &sample.chains { + let mut outcome = None; + for (depth, address) in chain.visited.iter().enumerate() { + // The seed itself is a member, not its own origin. + if depth == 0 { + continue; + } + if let Some(facts) = sample.facts.get(address) { + if let Some(found) = classifier.classify(facts) { + outcome = Some(found); + break; + } + } + } + let outcome = outcome.unwrap_or(Outcome::Unresolved { + reason: chain.stop.as_unresolved(), + depth: chain.visited.len().saturating_sub(1) as u32, + }); + census.record(&outcome); + results.push((chain.seed.clone(), outcome)); + } + + (results, census) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{classify::AnchorSet, edge::FundingEdge, outcome::Outcome}; + + const NOW: i64 = 1_800_000_000; + + fn wallet(address: &str) -> AddressFacts { + let mut f = AddressFacts::new(address); + f.owner = Some(SYSTEM_PROGRAM.to_string()); + f + } + + fn edge(sink: &str, source: &str, value: u64) -> FundingEdge { + FundingEdge { + sink: sink.into(), + sources: vec![source.into()], + value, + signature: "sig".into(), + slot: 1, + block_time: Some(NOW - 1_000), + ambiguous_attribution: false, + } + } + + fn sample_with(chains: Vec, facts: Vec) -> Sample { + Sample { + manifest: Manifest { + endpoint: "test".into(), + first_available_block: 0, + archival_probe_ok: true, + collected_at: NOW, + config: CollectionConfig::default(), + thresholds_hub_signatures: 5_000, + thresholds_distributor_fanout: 20, + thresholds_cluster_min_size: 3, + rpc_calls: 0, + excluded_non_wallet: Vec::new(), + ambiguous_attribution_rate: 0.0, + }, + chains, + facts: facts.into_iter().map(|f| (f.address.clone(), f)).collect(), + } + } + + #[test] + fn a_chain_resolves_at_the_first_terminal_past_the_seed() { + let mut program = wallet("Vault1"); + program.owner = Some("Whirlpoo1111111111111111111111111111111111".into()); + let sample = sample_with( + vec![Chain { + seed: "member".into(), + visited: vec!["member".into(), "hop1".into(), "Vault1".into()], + stop: ChainStop::LocalTerminal, + }], + vec![wallet("member"), wallet("hop1"), program], + ); + let (results, census) = + classify_sample(&sample, &AnchorSet::default(), &Thresholds::default(), NOW); + assert_eq!(results.len(), 1); + assert_eq!(results[0].1.label().unwrap(), "program:Vault1"); + assert_eq!(census.resolved, 1); + } + + /// The seed is the member being measured, so it must not classify itself. + /// Without this a member who happens to be a busy address would be its own + /// provenance class, which is meaningless. + #[test] + fn a_seed_is_never_its_own_origin() { + let mut busy_seed = wallet("member"); + busy_seed.signatures = SigCount::AtLeast(50_000); + busy_seed.first_seen = Some(NOW - 400 * 24 * 3_600); + + let sample = sample_with( + vec![Chain { + seed: "member".into(), + visited: vec!["member".into()], + stop: ChainStop::NoIncomingEdge, + }], + vec![busy_seed], + ); + let (results, _) = + classify_sample(&sample, &AnchorSet::default(), &Thresholds::default(), NOW); + assert!( + matches!( + results[0].1, + Outcome::Unresolved { + reason: Unresolved::NoIncomingEdge, + .. + } + ), + "the seed classified itself: {:?}", + results[0].1 + ); + } + + #[test] + fn every_chain_stop_maps_to_a_named_outcome() { + // No stop may fall through to a default, and an RPC failure must stay + // distinguishable from a genuine dead end. + for (stop, expected) in [ + (ChainStop::NoIncomingEdge, Unresolved::NoIncomingEdge), + (ChainStop::BelowThreshold, Unresolved::BelowThreshold), + (ChainStop::DepthExceeded, Unresolved::DepthExceeded), + (ChainStop::PageCapHit, Unresolved::PageCapHit), + (ChainStop::RpcFailure, Unresolved::RpcFailure), + ] { + assert_eq!(stop.as_unresolved(), expected, "{stop:?} mapped wrongly"); + } + } + + #[test] + fn an_rpc_failure_stays_a_failure_and_is_excluded_from_the_distribution() { + let sample = sample_with( + vec![Chain { + seed: "member".into(), + visited: vec!["member".into(), "hop1".into()], + stop: ChainStop::RpcFailure, + }], + vec![wallet("member"), wallet("hop1")], + ); + let (_, census) = + classify_sample(&sample, &AnchorSet::default(), &Thresholds::default(), NOW); + assert_eq!(census.rpc_failure, 1); + assert_eq!(census.measurable(), 0); + assert!( + !census.may_publish(), + "a run that is all failure must not publish" + ); + } + + /// Two members funded through the same exchange land in the same class even + /// though their chains differ in length. That is the whole mechanism. + #[test] + fn members_sharing_an_origin_share_a_class() { + let sample = sample_with( + vec![ + Chain { + seed: "alice".into(), + visited: vec!["alice".into(), "ExchangeHot".into()], + stop: ChainStop::LocalTerminal, + }, + Chain { + seed: "bob".into(), + visited: vec!["bob".into(), "hop".into(), "ExchangeHot".into()], + stop: ChainStop::LocalTerminal, + }, + ], + vec![ + wallet("alice"), + wallet("bob"), + wallet("hop"), + wallet("ExchangeHot"), + ], + ); + let anchors = AnchorSet::from_pairs([("ExchangeHot", "someexchange")]); + let (results, _) = classify_sample(&sample, &anchors, &Thresholds::default(), NOW); + assert_eq!(results[0].1.label(), results[1].1.label()); + assert_eq!(results[0].1.label().unwrap(), "entity:someexchange"); + } + + #[test] + fn a_sample_round_trips_through_json() { + let mut funded = wallet("funder"); + funded.birth_edge = Some(edge("funder", "upstream", 1_000_000)); + funded.funded = vec!["a".into(), "b".into()]; + let sample = sample_with( + vec![Chain { + seed: "member".into(), + visited: vec!["member".into(), "funder".into()], + stop: ChainStop::DepthExceeded, + }], + vec![wallet("member"), funded], + ); + let json = sample.to_json().unwrap(); + let back = Sample::from_json(&json).unwrap(); + assert_eq!( + sample, back, + "the committed artifact must survive a round trip" + ); + } + + /// The property the whole two-pass design exists for: analysis of a + /// committed sample is a pure function, so anyone can recompute the headline + /// without RPC access. + #[test] + fn classifying_the_same_sample_twice_gives_the_same_answer() { + let mut hub = wallet("Hub"); + hub.signatures = SigCount::AtLeast(50_000); + hub.first_seen = Some(NOW - 400 * 24 * 3_600); + let sample = sample_with( + vec![Chain { + seed: "member".into(), + visited: vec!["member".into(), "Hub".into()], + stop: ChainStop::LocalTerminal, + }], + vec![wallet("member"), hub], + ); + let a = classify_sample(&sample, &AnchorSet::default(), &Thresholds::default(), NOW); + let b = classify_sample(&sample, &AnchorSet::default(), &Thresholds::default(), NOW); + assert_eq!(a.0, b.0); + assert_eq!(a.1, b.1); + assert_eq!(a.0[0].1.label().unwrap(), "busy-unlabelled:Hub"); + } + + #[test] + fn the_default_edge_threshold_admits_account_creation() { + // Rent exemption for an empty account is about 890,880 lamports, so a + // threshold above it would discard the very event that creates a wallet. + let c = CollectionConfig::default(); + assert!( + c.min_edge_lamports < 890_880, + "the threshold would reject account creation itself" + ); + } +} diff --git a/data/sample-smoke.json b/data/sample-smoke.json new file mode 100644 index 00000000..0cf86394 --- /dev/null +++ b/data/sample-smoke.json @@ -0,0 +1,400 @@ +{ + "manifest": { + "endpoint": "https://api.mainnet-beta.solana.com", + "first_available_block": 0, + "archival_probe_ok": true, + "collected_at": 1784970512, + "config": { + "scope": "Sol", + "depth_max": 5, + "min_edge_lamports": 500000, + "sig_page_cap": 20, + "page_size": 1000 + }, + "thresholds_hub_signatures": 5000, + "thresholds_distributor_fanout": 20, + "thresholds_cluster_min_size": 3, + "rpc_calls": 263, + "excluded_non_wallet": [], + "ambiguous_attribution_rate": 0.0 + }, + "chains": [ + { + "seed": "BGT8Vm1u5nyW255LgWjD8wzsRs8g3KxBnB3Pm1FjC9fV", + "visited": [ + "BGT8Vm1u5nyW255LgWjD8wzsRs8g3KxBnB3Pm1FjC9fV" + ], + "stop": "PageCapHit" + }, + { + "seed": "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV", + "visited": [ + "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV" + ], + "stop": "PageCapHit" + }, + { + "seed": "7Q77q5ai1bjDA9kkMRMWJEJV7KWhVoBV7NDYKsAbcTd7", + "visited": [ + "7Q77q5ai1bjDA9kkMRMWJEJV7KWhVoBV7NDYKsAbcTd7" + ], + "stop": "PageCapHit" + }, + { + "seed": "BcK2Fr53rJ1YU9CuRPewu6idyzsXqnDqc1iYzsJtnnEf", + "visited": [ + "BcK2Fr53rJ1YU9CuRPewu6idyzsXqnDqc1iYzsJtnnEf" + ], + "stop": "PageCapHit" + }, + { + "seed": "3FuCiEjR6iZ9P7A6hSF2VFFwRq1Q19Gok7JgHf6gZkcf", + "visited": [ + "3FuCiEjR6iZ9P7A6hSF2VFFwRq1Q19Gok7JgHf6gZkcf" + ], + "stop": "PageCapHit" + }, + { + "seed": "EPzKe9HueqCag9Ca7qKimrFn7EUMFxQtvcVCyC9YgYoV", + "visited": [ + "EPzKe9HueqCag9Ca7qKimrFn7EUMFxQtvcVCyC9YgYoV" + ], + "stop": "PageCapHit" + }, + { + "seed": "5Ktwj8p43RvMsHdyBsx1ZZZrao3CQo4uT6uWqE4FRB11", + "visited": [ + "5Ktwj8p43RvMsHdyBsx1ZZZrao3CQo4uT6uWqE4FRB11" + ], + "stop": "PageCapHit" + }, + { + "seed": "JE9yvteYgfUagiYCgCCP9XTPsukrFRMXL8o34DAwF7hf", + "visited": [ + "JE9yvteYgfUagiYCgCCP9XTPsukrFRMXL8o34DAwF7hf", + "7TH25K4sqPJVqoBqWjUx39bUU7V6SFePfvJ4jRji84qa", + "FnRAkDK9i1FUbeJT7L51Uv4N1apEwiSPtraBGYH8d58P", + "4FhqKqW5UndCQA9CdNBf99UXPvVz2UWADs3nWJDuxGqV", + "GowCUanu8Pn8eFwUwP8LhTKs5dqs2UcUCoNefrxPnin1", + "85YGfmqnNpR3qs4i2Jie3PogFfqH8HKqiGmaKaDH7EVB" + ], + "stop": "DepthExceeded" + }, + { + "seed": "6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn", + "visited": [ + "6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn" + ], + "stop": "PageCapHit" + }, + { + "seed": "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ", + "visited": [ + "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ" + ], + "stop": "PageCapHit" + }, + { + "seed": "DRg3C1nVnvwugDW1J9gnhArcZpo7oyREwfugZCLjPJVc", + "visited": [ + "DRg3C1nVnvwugDW1J9gnhArcZpo7oyREwfugZCLjPJVc" + ], + "stop": "PageCapHit" + }, + { + "seed": "Fqwus6SoxhNzxESj6ftBRiDWvGMB2ZYStgtKtDwe9iyS", + "visited": [ + "Fqwus6SoxhNzxESj6ftBRiDWvGMB2ZYStgtKtDwe9iyS" + ], + "stop": "PageCapHit" + } + ], + "facts": { + "3FuCiEjR6iZ9P7A6hSF2VFFwRq1Q19Gok7JgHf6gZkcf": { + "address": "3FuCiEjR6iZ9P7A6hSF2VFFwRq1Q19Gok7JgHf6gZkcf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "4FhqKqW5UndCQA9CdNBf99UXPvVz2UWADs3nWJDuxGqV": { + "address": "4FhqKqW5UndCQA9CdNBf99UXPvVz2UWADs3nWJDuxGqV", + "owner": null, + "executable": false, + "signatures": { + "Exact": 508 + }, + "first_seen": 1784175985, + "birth_edge": { + "sink": "4FhqKqW5UndCQA9CdNBf99UXPvVz2UWADs3nWJDuxGqV", + "sources": [ + "GowCUanu8Pn8eFwUwP8LhTKs5dqs2UcUCoNefrxPnin1" + ], + "value": 1056265607849, + "signature": "514swBZDAqyDDLknQX94SaowFTEuGxXvyfEZLzFeZNGweAqHusmuAjeECZiZCZ8mcqHS1zcvZFGKXiQ8s2JPvj8A", + "slot": 433202358, + "block_time": 1784175985, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GowCUanu8Pn8eFwUwP8LhTKs5dqs2UcUCoNefrxPnin1" + ], + "funded": [ + "FnRAkDK9i1FUbeJT7L51Uv4N1apEwiSPtraBGYH8d58P" + ] + }, + "5Ktwj8p43RvMsHdyBsx1ZZZrao3CQo4uT6uWqE4FRB11": { + "address": "5Ktwj8p43RvMsHdyBsx1ZZZrao3CQo4uT6uWqE4FRB11", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV": { + "address": "62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn": { + "address": "6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "7Q77q5ai1bjDA9kkMRMWJEJV7KWhVoBV7NDYKsAbcTd7": { + "address": "7Q77q5ai1bjDA9kkMRMWJEJV7KWhVoBV7NDYKsAbcTd7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "7TH25K4sqPJVqoBqWjUx39bUU7V6SFePfvJ4jRji84qa": { + "address": "7TH25K4sqPJVqoBqWjUx39bUU7V6SFePfvJ4jRji84qa", + "owner": null, + "executable": false, + "signatures": { + "Exact": 530 + }, + "first_seen": 1784177508, + "birth_edge": { + "sink": "7TH25K4sqPJVqoBqWjUx39bUU7V6SFePfvJ4jRji84qa", + "sources": [ + "FnRAkDK9i1FUbeJT7L51Uv4N1apEwiSPtraBGYH8d58P" + ], + "value": 941607555363, + "signature": "3c9CjJza7Ymcig6cN8b7X2ntiVDEDcYr4Sw1Rjs8GnmcJ5Nqc9eDt1XvkExXPTFtgX1re7HZSmMgf3Z1F7mRMpX8", + "slot": 433206066, + "block_time": 1784177508, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FnRAkDK9i1FUbeJT7L51Uv4N1apEwiSPtraBGYH8d58P" + ], + "funded": [ + "JE9yvteYgfUagiYCgCCP9XTPsukrFRMXL8o34DAwF7hf" + ] + }, + "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ": { + "address": "7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "85YGfmqnNpR3qs4i2Jie3PogFfqH8HKqiGmaKaDH7EVB": { + "address": "85YGfmqnNpR3qs4i2Jie3PogFfqH8HKqiGmaKaDH7EVB", + "owner": null, + "executable": false, + "signatures": { + "Exact": 610 + }, + "first_seen": 1784165318, + "birth_edge": { + "sink": "85YGfmqnNpR3qs4i2Jie3PogFfqH8HKqiGmaKaDH7EVB", + "sources": [ + "CRS4fEoMQDbYcUL97fi6FdMJMt862X7AsWd1UpXa7peg" + ], + "value": 977878830440, + "signature": "oNjG1piYcxazmjmHH7MCEPABsDjm8W22Mnx2LgoBVrtRzwGxuXxYkLaufZYSWTPugfhKqaQZx9jpJDk62cJCV8P", + "slot": 433176446, + "block_time": 1784165318, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CRS4fEoMQDbYcUL97fi6FdMJMt862X7AsWd1UpXa7peg" + ], + "funded": [ + "GowCUanu8Pn8eFwUwP8LhTKs5dqs2UcUCoNefrxPnin1" + ] + }, + "BGT8Vm1u5nyW255LgWjD8wzsRs8g3KxBnB3Pm1FjC9fV": { + "address": "BGT8Vm1u5nyW255LgWjD8wzsRs8g3KxBnB3Pm1FjC9fV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "BcK2Fr53rJ1YU9CuRPewu6idyzsXqnDqc1iYzsJtnnEf": { + "address": "BcK2Fr53rJ1YU9CuRPewu6idyzsXqnDqc1iYzsJtnnEf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "DRg3C1nVnvwugDW1J9gnhArcZpo7oyREwfugZCLjPJVc": { + "address": "DRg3C1nVnvwugDW1J9gnhArcZpo7oyREwfugZCLjPJVc", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "EPzKe9HueqCag9Ca7qKimrFn7EUMFxQtvcVCyC9YgYoV": { + "address": "EPzKe9HueqCag9Ca7qKimrFn7EUMFxQtvcVCyC9YgYoV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "FnRAkDK9i1FUbeJT7L51Uv4N1apEwiSPtraBGYH8d58P": { + "address": "FnRAkDK9i1FUbeJT7L51Uv4N1apEwiSPtraBGYH8d58P", + "owner": null, + "executable": false, + "signatures": { + "Exact": 110 + }, + "first_seen": 1784177001, + "birth_edge": { + "sink": "FnRAkDK9i1FUbeJT7L51Uv4N1apEwiSPtraBGYH8d58P", + "sources": [ + "4FhqKqW5UndCQA9CdNBf99UXPvVz2UWADs3nWJDuxGqV" + ], + "value": 1013755363812, + "signature": "4bCLrfEZQDfnnTB2ZtKjibJMGxc2i9dmQfBw3YwspS9nj7ECNGe6ZLKnvLBzwLs17rLfpazETh5jFj3NeKBR2m9S", + "slot": 433204823, + "block_time": 1784177001, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4FhqKqW5UndCQA9CdNBf99UXPvVz2UWADs3nWJDuxGqV" + ], + "funded": [ + "7TH25K4sqPJVqoBqWjUx39bUU7V6SFePfvJ4jRji84qa" + ] + }, + "Fqwus6SoxhNzxESj6ftBRiDWvGMB2ZYStgtKtDwe9iyS": { + "address": "Fqwus6SoxhNzxESj6ftBRiDWvGMB2ZYStgtKtDwe9iyS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "GowCUanu8Pn8eFwUwP8LhTKs5dqs2UcUCoNefrxPnin1": { + "address": "GowCUanu8Pn8eFwUwP8LhTKs5dqs2UcUCoNefrxPnin1", + "owner": null, + "executable": false, + "signatures": { + "Exact": 281 + }, + "first_seen": 1784175331, + "birth_edge": { + "sink": "GowCUanu8Pn8eFwUwP8LhTKs5dqs2UcUCoNefrxPnin1", + "sources": [ + "85YGfmqnNpR3qs4i2Jie3PogFfqH8HKqiGmaKaDH7EVB" + ], + "value": 1013718534543, + "signature": "275rMzAXjBMFqoAbmxFDKbrCJhxKkvkTPr8NTkfRXef7jp77aLpLJ7ys64DrZoeNF6mRZCnv5nkcU5TwLt7RK4L4", + "slot": 433200756, + "block_time": 1784175331, + "ambiguous_attribution": false + }, + "fee_payers": [ + "85YGfmqnNpR3qs4i2Jie3PogFfqH8HKqiGmaKaDH7EVB" + ], + "funded": [ + "4FhqKqW5UndCQA9CdNBf99UXPvVz2UWADs3nWJDuxGqV" + ] + }, + "JE9yvteYgfUagiYCgCCP9XTPsukrFRMXL8o34DAwF7hf": { + "address": "JE9yvteYgfUagiYCgCCP9XTPsukrFRMXL8o34DAwF7hf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12758 + }, + "first_seen": 1784184343, + "birth_edge": { + "sink": "JE9yvteYgfUagiYCgCCP9XTPsukrFRMXL8o34DAwF7hf", + "sources": [ + "7TH25K4sqPJVqoBqWjUx39bUU7V6SFePfvJ4jRji84qa" + ], + "value": 31452400000, + "signature": "47b3ovtAmBQU129wgtij6M2qLZrQJszUK3gyZeaj9rNFLEobqFfsc9D4NjWnh3bG3DxmWo8iatDgy4Jpcu5SbcdL", + "slot": 433222637, + "block_time": 1784184343, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7TH25K4sqPJVqoBqWjUx39bUU7V6SFePfvJ4jRji84qa" + ], + "funded": [] + } + } +} \ No newline at end of file diff --git a/data/seeds-smoke.txt b/data/seeds-smoke.txt new file mode 100644 index 00000000..b1203cd6 --- /dev/null +++ b/data/seeds-smoke.txt @@ -0,0 +1,12 @@ +BGT8Vm1u5nyW255LgWjD8wzsRs8g3KxBnB3Pm1FjC9fV +62qc2CNXwrYqQScmEdiZFFAnJR262PxWEuNQtxfafNgV +7Q77q5ai1bjDA9kkMRMWJEJV7KWhVoBV7NDYKsAbcTd7 +BcK2Fr53rJ1YU9CuRPewu6idyzsXqnDqc1iYzsJtnnEf +3FuCiEjR6iZ9P7A6hSF2VFFwRq1Q19Gok7JgHf6gZkcf +EPzKe9HueqCag9Ca7qKimrFn7EUMFxQtvcVCyC9YgYoV +5Ktwj8p43RvMsHdyBsx1ZZZrao3CQo4uT6uWqE4FRB11 +JE9yvteYgfUagiYCgCCP9XTPsukrFRMXL8o34DAwF7hf +6TxjC5wJzuuZgTtnTMipwwULEbMPx5JPW3QwWkdTGnrn +7VtfL8fvgNfhz17qKRMjzQEXgbdpnHHHQRh54R9jP2RJ +DRg3C1nVnvwugDW1J9gnhArcZpo7oyREwfugZCLjPJVc +Fqwus6SoxhNzxESj6ftBRiDWvGMB2ZYStgtKtDwe9iyS diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100644 index 00000000..de820fe8 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,220 @@ +# Architecture + +## What the protocol is + +An anonymity set for *actions*. Members deposit a fixed denomination; later, a +member proves in zero knowledge that they own some note in the set and directs +the pool to act. The pool executes. An observer sees that an action happened and +cannot say which member asked for it. + +Four crates and one program: + +``` +mirror-core field, Poseidon, Merkle accumulator, notes (linked on-chain) +mirror-circuit R1CS gadget, membership circuit, prover (host only) +mirror-pool the on-chain program +mirror-provenance funding-provenance measurement (host only) +mirror-cli setup, endpoint check, collect, analyze +``` + +`mirror-core` is shared by the program and the host deliberately: a commitment, +a nullifier and an action binding each have exactly one implementation, so the +two sides cannot compute different answers and discover it in production. + +## The note model + +``` +note = (k, r) +commitment = H3(k, r, denom_tag) the Merkle leaf +nullifier = H1(k) revealed on spend, spent once ever +``` + +**The denomination is a pool constant, not a field in the note.** One pool serves +one denomination, so the escrowed lamports and the hidden commitment cannot +disagree. This is not a stylistic choice — it makes a class of drain +unrepresentable rather than merely untested. Two competing submissions in this +bounty are drainable at exactly this point: one escrows an amount that is never +bound to its commitment, so a depositor of one lamport can withdraw the whole +pool with an entirely valid proof; the other issues an epoch-scoped nullifier +against a value payout, so one deposit pays out once per epoch forever. + +Nullifiers here are spend-once, never epoch-scoped. + +### Domain separation by arity + +Poseidon instances of different width are different permutations, so arity is +itself a domain separator and a free one: + +| value | arity | +|---|---| +| nullifier | 1 | +| Merkle node | 2 | +| note commitment | 3 | +| action binding | 4 | + +An integer tag was the first design and it was wrong: with a small tag constant, +a Merkle node whose left child equals the tag collides with a nullifier. +Reaching that state needs a Poseidon preimage, so it was not exploitable — but +arity separation removes the question rather than bounding it. + +## The accounting invariant + +``` +vault.lamports >= denomination × (deposits − settled_spends) + rent +``` + +The amount owed is a function of two counters and the pool's constant +denomination. Nothing a prover supplies can influence it. It is re-read from the +vault *after* lamports move rather than inferred from the arithmetic that moved +them, and the spend counter refuses to exceed the deposit counter outright. + +Escrow lives in its own vault PDA holding no data, so the invariant reads against +a balance containing nothing but escrow and its own rent. Entry fees accrue on +the pool account instead, where they can never be mistaken for lamports backing +an unspent note. + +## The circuit + +*I know `(k, r, denom_tag)` such that `H3(k, r, denom_tag)` is a leaf of the tree +with root `R`, my nullifier is `H1(k)`, and this proof is bound to `action`.* + +Three public inputs, and that is a cost decision. On-chain verification measures +at `74,179 + 5,661 × N` compute units, so each input costs about 5.7k CU. + +| public input | why it cannot be a witness | +|---|---| +| `root` | the program checks it against its own root history | +| `nullifier` | the program records it to prevent replay | +| `action_binding` | the program recomputes it from the action it executes | + +`denom_tag` stays a witness because Merkle membership already constrains it: a +pool's tree only ever contains leaves committed at that pool's denomination. + +The action binding is squared under constraint. A Groth16 public input +participates in verification only through the R1CS columns that reference it; an +input used in no constraint has an all-zero column, its `gamma_abc` term is the +identity, and *any* value satisfies the equation. Without that one constraint a +relay could swap the action after proving and the proof would still verify. +`tests/onchain_layout.rs` tampers with that exact input and asserts the real +verifier rejects it, so the property is checked rather than reasoned about. + +### Three-way parity + +The gadget, the host and the syscall must compute one function. `solana-poseidon` +is the only Poseidon entry point, and it is cfg-gated upstream to the syscall +on-chain and to light-poseidon off-chain, so host and program agree by +construction. The R1CS gadget then reads light-poseidon's published round +constants rather than re-deriving them. + +All three are checked against circomlib's published `poseidon([1,2])` vector +rather than against each other, so all three agreeing on a wrong answer is not a +reachable state. Several published Solana projects ship a gadget whose native and +in-circuit hashes are different functions; that failure only appears at proving +time, and this is the test that catches it. + +A pure-Rust Poseidon on SBF overflows the 4 KB stack frame and costs roughly +1,500× the syscall even where codegen lets it complete, so no arkworks code is +linked into the program. + +## Instructions + +**`init_pool`** — permissionless. One pool per denomination, globally: splitting +deposits of the same size across pools splits the anonymity set, and a split set +is worse for every member in it. There is no privileged authority, so no key +whose loss freezes the escrow. + +**`deposit`** — escrows exactly the pool's denomination, read from the pool and +never from the instruction, and appends the commitment to the accumulator. + +**`submit_spend`** — verifies the Groth16 proof on-chain, burns the nullifier, +records the authorised action. Pays out nothing. + +The action binding is never transmitted. It is recomputed on-chain from the +selector, the beneficiary and the relay fee and used as the third public input, +so a relay that alters any of them produces a different binding and the pairing +fails. There is no separate field that could be checked incorrectly. + +The relay signs, never the member. A member paying their own fee would sign with +their own wallet and destroy their own anonymity, so no member key appears on +chain on this path. + +**`settle_epoch`** — executes a batch in one transaction so every payout shares a +timestamp and an ordering. + +The crowd rule is conditional: a batch needs `k_floor` spends, **or** every spend +in it must have waited out an hour. Requiring the crowd unconditionally is a +liveness hazard — a quiet pool could hold a member's funds until a crowd that +never comes. Dropping it makes "synchronised" a word rather than a property. + +### The permissionless exit + +There is no `self_spend` instruction because none is needed. A member acts as +their own relay with a zero fee, and settlement is already permissionless, so +they settle their own batch once the timeout passes. Nothing in the protocol can +hold their escrow. + +The cost is the expected one: their own wallet signs, giving up the anonymity the +relay path provides. It is an escape hatch, not a mode of operation. + +## What the k floor does and does not do + +`k_floor` bounds **program-visible membership**: how many notes the tree holds. +That is all a program can check, because the thing that actually shrinks an +anonymity set is not visible on chain. + +An observer can partition members by where their capital came from. Learning a +member's funding class leaves only that class to guess within, so the anonymity +that survives is the size of the class rather than `k`. No deposit pool controls +where its users' money came from. + +So the protocol does two things about it, and claims exactly those two: + +1. **The action side is closed.** Actions are executed by the pool PDA, so the + on-chain funding trace of an action leads to the pool and is identical for + every member. +2. **The membership side is measured.** `mirror-provenance` computes it from real + chain data, and the method and its limits are published with the number. + +## The measurement + +Two passes, and the split is the point. + +**Pass one** walks each member's funding chain by the birth edge — the oldest +value credit, the event that created the account — and writes everything it +observed to a sample file. It is the only networked step. + +**Pass two** classifies over the complete sample, offline. Two of the five +terminal rules are properties of the *set* rather than of an address, so +classifying during traversal would make a member's class depend on visit order. +Given the same sample, pass two always produces the same partition, which is what +lets someone else check a published number without RPC access. + +Design choices that exist to avoid specific published defects: + +- Edges come from **balance deltas**, not instruction parsing, which is blind to + every program that moves lamports by direct account mutation. +- The **birth edge** is the oldest credit. A competing tracer scans the six most + *recent* transactions, which is the wrong end of the history for anything with + more than six. +- The **hub threshold is decoupled from the paging cap**. In a competing tracer + the two are the same number, so "reaches an attributable origin" there means + "hit the RPC page cap". +- **RPC failures are never evidence.** They are counted separately and excluded + from the distribution, and above a 1% failure rate the run refuses to print a + headline rather than printing a warning above one. +- The endpoint is **checked before the run**. A truncated endpoint returns `null` + rather than an error for pruned history, so a collection against one would look + healthy and report every old funding event as absent. +- Seeds that are not wallets are excluded on a **definitional** criterion and the + count is published. Excluding addresses for looking hard to trace would be a + different thing and is the bias that inflates a competing measurement. + +The headline is the loss factor `ρ = 2^−H(C)` rather than effective-k, because it +is independent of `k` and therefore comparable across pools. Effective-k measured +at small `k` systematically understates the steady-state loss and cannot be +extrapolated upward. + +`2^H(C)` — entropy over the class-size distribution — is widely quoted as the +effective anonymity set and is **inverted**: it is maximised when every member +stands alone, which is total deanonymisation. It is the leakage. The anonymity is +`2^H(X|C)`. diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md new file mode 100644 index 00000000..2429b957 --- /dev/null +++ b/docs/MEASUREMENT_LOG.md @@ -0,0 +1,76 @@ +# Measurement log + +Every collection run, including the ones that produced nothing. A measurement +project that only records its successful runs is not reporting, it is selecting. + +--- + +## Run 1 — 2026-07-25, pipeline validation, no publishable result + +**Purpose.** End-to-end validation of collect → analyze against live mainnet. + +**Frame.** 12 addresses credited at least 0.001 SOL in a recent finalised block, +filtered to accounts owned by the system program and currently existing. + +**Endpoint.** `api.mainnet-beta.solana.com`, first available block 0, archival +probe at slot 50,000,000 passed. 263 RPC calls, 0 failures. + +**Result.** + +``` +attempted 12 | resolved 0 | evidence-unresolved 0 | budget-unresolved 12 + | scope-unresolved 0 | rpc failures 0 (0.00%) +``` + +No headline. The tool reported nothing rather than reporting something. + +### What went wrong, and it was the frame + +Eleven of twelve seeds hit the signature page cap at 20,000 lifetime +transactions. They are system-owned wallets, so they passed the frame filter, but +they are not people: at that volume they are market makers, arbitrage bots and +exchange hot wallets. + +The cause is the sampling design, not the collector. **Sampling addresses because +they appear credited in a block is transaction-weighted, not member-weighted.** +An address that transacts a thousand times a day is a thousand times more likely +to appear in any given block than one that transacts daily, so the frame is +size-biased toward exactly the addresses whose funding history is most expensive +to walk. It is the same error as estimating how often people travel by asking +people at a bus stop. + +For measuring a pool, the frame is the pool's depositors, each counted once — +which is member-weighted by construction. + +### What this run does establish + +- The endpoint precondition, the collector, the two-pass split and the metric + ladder all work against live mainnet. +- Page-capped addresses are reported as **budget-unresolved**, kept distinct from + evidence-unresolved and from RPC failure. The distinction is what makes this + run diagnosable instead of a number. +- The tool refused to print a headline from a run where nothing resolved. + +### A defect this run caught + +An earlier attempt returned 14 RPC calls for 12 seeds and reported everything +unresolved. Frame validation fetches each seed's owner before tracing, and the +collector's observation cache treated "we know the owner" as "we have observed +this address", so it skipped signature paging entirely and never looked for a +birth edge. + +The bug was visible only because the call count was reported and the census was +honest. A collector that defaulted a failed or skipped lookup to "no history" — +as a published tracer in this space does — would have produced a full set of +unresolved members and a plausible-looking headline, with nothing to indicate +anything was wrong. + +--- + +## Run 2 — planned + +**Frame.** Depositors of a live pool, enumerated from its deposit instructions, +each address counted once regardless of how often it transacts. + +This is the member-weighted frame, and it is the only one from which an +effective-k for that pool means anything. From 2f01896f23b1b7c010005dd4bd68b38d69916ab8 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 06:26:45 -0300 Subject: [PATCH 19/83] feat(provenance): member-weighted frame construction from a pool's depositors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The frame decides what a measurement is of, and getting it wrong is the easiest way to produce a confident number about the wrong population. Our own first run did exactly that, so frame construction is now a command rather than a script: auditable, testable, and recorded in the manifest. Each depositor appears once however often they transact. Sampling addresses because they appear in recent blocks is size-biased — an address transacting a thousand times a day is a thousand times likelier to be drawn — which is how a frame fills with market makers instead of members. The sample is spread across the pool's whole signature history by walking pages backwards and striding within each page, rather than taken from its most recent minute. A published measurement in this space draws its entire sample from nine consecutive slots, roughly four seconds of chain time, and reports Wilson intervals over it as though it were a population sample; the run here reports the slot span it actually covered so a reader can judge that for themselves. Nothing is excluded for being hard to trace. Busy depositors stay in, because dropping them would inflate the resolved fraction by construction, and that is the bias that makes a competing measurement's untraceable bucket look like a finding about the pool rather than about its sampler. Withdrawals are distinguished from deposits by requiring the fee payer to have parted with value beyond the fee and something else in the transaction to have gained, so a relayer moving nothing of their own never enters the frame as a member. --- crates/mirror-cli/src/main.rs | 114 ++++++++++++++++++++++++ crates/mirror-provenance/src/frame.rs | 121 ++++++++++++++++++++++++++ crates/mirror-provenance/src/lib.rs | 2 + 3 files changed, 237 insertions(+) create mode 100644 crates/mirror-provenance/src/frame.rs diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index eb6eec05..50c66528 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -54,6 +54,32 @@ enum Command { #[arg(long, default_value_t = 0.4)] rps: f64, }, + /// Builds a member-weighted frame from a pool's depositors. + /// + /// Each depositor appears once however often they transact, and the sample + /// is spread across the pool's whole signature history rather than its most + /// recent minute. Nothing is dropped for looking hard to trace. + Seeds { + /// The pool program to enumerate depositors of. + #[arg(long)] + program: String, + /// How many distinct depositors to collect. + #[arg(long, default_value_t = 40)] + n: usize, + /// Signature pages to walk back through. More pages means a sample + /// spread over more of the pool's lifetime. + #[arg(long, default_value_t = 8)] + pages: u32, + /// Minimum lamports a payer must part with for it to count as a deposit. + #[arg(long, default_value_t = 10_000_000)] + min_deposit: u64, + #[arg(long, default_value = "seeds.txt")] + out: PathBuf, + #[arg(long, default_value = "https://api.mainnet-beta.solana.com")] + endpoint: String, + #[arg(long, default_value_t = 0.4)] + rps: f64, + }, /// Pass one: walks each seed's funding chain and writes a sample file. /// /// The only step that touches the network. Everything it observes goes into @@ -141,6 +167,94 @@ fn main() -> Result<()> { } } } + Command::Seeds { + program, + n, + pages, + min_deposit, + out, + endpoint, + rps, + } => { + let mut client = mirror_provenance::RpcClient::new(&endpoint, rps); + let check = client + .check_preconditions() + .map_err(|e| anyhow::anyhow!("endpoint refused: {e}"))?; + eprintln!( + "endpoint ok (first available block {})", + check.first_available_block + ); + + let mut seeds: Vec = Vec::new(); + let mut seen = std::collections::BTreeSet::new(); + let mut before: Option = None; + let mut scanned = 0usize; + let mut slots: Vec = Vec::new(); + + 'paging: for page in 0..pages { + let batch = client + .signatures_for_address(&program, before.as_deref(), 1_000) + .map_err(|e| anyhow::anyhow!("listing program signatures: {e}"))?; + if batch.is_empty() { + break; + } + before = Some(batch.last().expect("non-empty").signature.clone()); + eprintln!( + "page {page}: {} signatures, {} depositors so far", + batch.len(), + seeds.len() + ); + + // Take from across the page rather than only its head, so the + // sample spans the page's time range instead of clustering. + let want_from_page = (n - seeds.len()).div_ceil((pages - page).max(1) as usize); + let stride = (batch.len() / want_from_page.max(1)).max(1); + for info in batch.iter().step_by(stride) { + if info.err { + continue; + } + scanned += 1; + let tx = match client.transaction(&info.signature) { + Ok(t) => t, + // A failure here drops one candidate; it never becomes a + // claim about the pool. + Err(_) => continue, + }; + if let Some(d) = mirror_provenance::depositor_of(&tx, min_deposit) { + if seen.insert(d.clone()) { + slots.push(tx.slot); + seeds.push(d); + if seeds.len() >= n { + break 'paging; + } + } + } + } + } + + anyhow::ensure!(!seeds.is_empty(), "no depositors found for {program}"); + std::fs::write(&out, format!("{}\n", seeds.join("\n"))) + .with_context(|| format!("writing {}", out.display()))?; + + let span = match (slots.iter().min(), slots.iter().max()) { + (Some(lo), Some(hi)) => hi - lo, + _ => 0, + }; + println!( + "wrote {} ({} distinct depositors)", + out.display(), + seeds.len() + ); + println!( + "scanned {scanned} transactions, {} rpc calls", + client.calls_made() + ); + println!( + "sample spans {span} slots (~{:.1} hours of chain time)", + span as f64 * 0.4 / 3600.0 + ); + Ok(()) + } Command::Collect { seeds, out, diff --git a/crates/mirror-provenance/src/frame.rs b/crates/mirror-provenance/src/frame.rs new file mode 100644 index 00000000..b2742aaa --- /dev/null +++ b/crates/mirror-provenance/src/frame.rs @@ -0,0 +1,121 @@ +//! Building a member-weighted frame from a pool's depositors. +//! +//! The frame decides what the measurement is *of*, and getting it wrong is the +//! easiest way to produce a confident number about the wrong population. +//! +//! **Member-weighted, not transaction-weighted.** Each depositor appears once +//! however often they transact. Sampling addresses because they appear in recent +//! blocks is size-biased: an address that transacts a thousand times a day is a +//! thousand times likelier to be drawn than one that transacts daily, so the +//! frame fills with market makers and bots. Our own first run did exactly this +//! and produced nothing; `docs/MEASUREMENT_LOG.md` records it. +//! +//! **Spread across the pool's history, not its last minute.** Pages are walked +//! backwards through the whole signature history and depositors are taken from +//! every page. A published measurement in this space draws its entire sample +//! from a nine-slot window — about four seconds of chain time — and reports +//! Wilson intervals over it as though it were an independent sample of a +//! population. +//! +//! **Nothing is excluded for being hard to trace.** Busy depositors stay in. +//! Dropping them would inflate the resolved fraction by construction, which is +//! the bias that makes a competing measurement's untraceable bucket look like a +//! finding about the pool. + +use crate::edge::TransactionView; + +/// The address that funded a deposit, if this transaction looks like one. +/// +/// A deposit debits the payer and credits the pool. We take the fee payer, whose +/// index is zero, and require that they lost at least `min_lamports` beyond the +/// fee — so a withdrawal, in which the payer is a relayer moving nothing of +/// their own, is not mistaken for a deposit. +pub fn depositor_of(tx: &TransactionView, min_lamports: u64) -> Option { + let deltas = tx.deltas()?; + let payer_delta = *deltas.first()?; + if payer_delta >= 0 { + return None; + } + if payer_delta.unsigned_abs() < min_lamports as u128 { + return None; + } + // Something else in the transaction must have gained: a payer who only + // burned lamports has not deposited anywhere. + if !deltas.iter().skip(1).any(|d| *d > 0) { + return None; + } + tx.account_keys.first().cloned() +} + +#[cfg(test)] +mod tests { + use super::*; + + fn tx(keys: &[&str], pre: &[u64], post: &[u64], fee: u64) -> TransactionView { + TransactionView { + signature: "sig".into(), + slot: 1, + block_time: Some(1_700_000_000), + account_keys: keys.iter().map(|s| s.to_string()).collect(), + pre_balances: pre.to_vec(), + post_balances: post.to_vec(), + fee, + } + } + + const MIN: u64 = 10_000_000; + + #[test] + fn a_deposit_names_its_payer() { + // The payer sends 1 SOL into the pool vault. + let t = tx( + &["depositor", "pool_vault"], + &[2_000_000_000, 5_000_000_000], + &[999_995_000, 6_000_000_000], + 5_000, + ); + assert_eq!(depositor_of(&t, MIN).as_deref(), Some("depositor")); + } + + #[test] + fn a_withdrawal_is_not_a_deposit() { + // A relayer pays the fee; the pool pays a recipient. The relayer moved + // nothing of their own and must not enter the frame as a member. + let t = tx( + &["relayer", "pool_vault", "recipient"], + &[1_000_000_000, 5_000_000_000, 0], + &[999_995_000, 4_000_000_000, 1_000_000_000], + 5_000, + ); + assert!(depositor_of(&t, MIN).is_none()); + } + + #[test] + fn a_payer_who_only_burned_the_fee_is_not_a_depositor() { + let t = tx( + &["payer", "other"], + &[1_000_000_000, 7], + &[999_995_000, 7], + 5_000, + ); + assert!(depositor_of(&t, MIN).is_none()); + } + + #[test] + fn a_transfer_below_the_threshold_is_not_a_deposit() { + let t = tx( + &["payer", "sink"], + &[1_000_000_000, 0], + &[999_994_000, 1_000], + 5_000, + ); + assert!(depositor_of(&t, MIN).is_none()); + } + + #[test] + fn a_malformed_transaction_yields_nothing_rather_than_a_guess() { + let mut t = tx(&["a", "b"], &[1, 2], &[1, 2], 0); + t.post_balances.pop(); + assert!(depositor_of(&t, MIN).is_none()); + } +} diff --git a/crates/mirror-provenance/src/lib.rs b/crates/mirror-provenance/src/lib.rs index 86644f9a..45dc2b6e 100644 --- a/crates/mirror-provenance/src/lib.rs +++ b/crates/mirror-provenance/src/lib.rs @@ -12,6 +12,7 @@ pub mod classify; pub mod edge; pub mod facts; +pub mod frame; pub mod metrics; pub mod outcome; pub mod rpc; @@ -20,6 +21,7 @@ pub mod trace; pub use classify::{AnchorSet, Classifier, SetStructure, Thresholds}; pub use edge::{FundingEdge, TransactionView}; pub use facts::{AddressFacts, SigCount}; +pub use frame::depositor_of; pub use metrics::Anonymity; pub use outcome::{Census, Outcome, TerminalRule, Unresolved}; pub use rpc::{RpcClient, RpcError}; From 06664aa5a9929f7f542d3fd46970cceb6e4dcf80 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 06:28:04 -0300 Subject: [PATCH 20/83] docs: README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leads with the problem every serious submission in this bounty identifies and none closes — that an anonymity set on a public ledger can be partitioned by where each member's capital came from — and states the two things this submission claims about it rather than implying a third. Includes a section of what is deliberately not claimed, because in a bounty that asks for measured rather than asserted, the boundary is part of the result. --- README.md | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..91f7c86c --- /dev/null +++ b/README.md @@ -0,0 +1,169 @@ +# mirror-pool + +A behavioral anonymity set for Solana. Members deposit a fixed denomination; +later, a member proves in zero knowledge that they own some note in the set and +directs the pool to act. The pool executes. An observer sees that an action +happened and cannot say which member asked for it. + +Rust end to end. MIT. No Anchor, no Circom, no JavaScript anywhere in the +proving path. + +``` +make verify # fmt, clippy -D warnings, tests, build-sbf +``` + +## The problem this takes as its subject + +Every serious submission to this bounty — across all three repositories — +identifies the same open channel and none of them closes it: + +> An anonymity set on a public ledger can be partitioned by **where each member's +> capital came from**. Learning a member's funding class leaves only that class to +> guess within, so what survives is the size of the class, not `k`. + +No deposit pool controls where its users' money came from, so this cannot be +fixed by a better circuit. What it can be is *measured*, and measured honestly. + +So this submission claims exactly two things: + +1. **The action side is closed.** Actions execute from the pool PDA, so an + action's on-chain funding trace leads to the pool and is identical for every + member. +2. **The membership side is measured**, from real mainnet data, with the method + and its limits published beside the number. + +Anything we cannot support with a measurement whose method is published, we do +not say. There is a section below of things we deliberately do not claim. + +## What is here + +| | | +|---|---| +| `programs/mirror-pool` | The on-chain program. Groth16 verified on-chain at **99k CU**. | +| `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain. | +| `crates/mirror-circuit` | R1CS gadget, membership circuit, prover, key export. | +| `crates/mirror-provenance` | The funding-provenance measurement. | +| `crates/mirror-cli` | `setup`, `check-endpoint`, `seeds`, `collect`, `analyze`. | + +**152 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +produces into a real SVM, sends real transactions, and verifies a real Groth16 +proof through the actual syscall — so a divergence between what the host believes +and what the chain does cannot pass unnoticed. + +## Properties, and how each is checked + +**A note's value cannot disagree with its commitment.** The denomination is a +pool constant rather than a field in the note, so `vault ≥ denomination × +outstanding` is a function of two counters that nothing a prover supplies can +influence. It is re-read from the vault after lamports move rather than inferred +from the arithmetic that moved them. + +**A nullifier is spent once, ever** — never epoch-scoped. + +Those two together make a class of drain *unrepresentable* rather than merely +untested. Two competing submissions are drainable at exactly this point: one +escrows an amount never bound to its hidden commitment, so a depositor of one +lamport can withdraw the whole pool with a valid proof; the other issues an +epoch-scoped nullifier against a value payout, so one deposit pays out once per +epoch forever. + +**A relay cannot redirect or re-price an action.** The action binding is never +transmitted — it is recomputed on-chain from the selector, the beneficiary and +the relay fee and used as the third public input, so altering any of them changes +the binding and the pairing fails. There is no separate field that could be +checked incorrectly. The test tampers with that exact input and asserts the real +verifier rejects it. + +**No member key ever appears on chain** on the relay path. + +**Nothing can hold a member's escrow.** There is no `self_spend` instruction +because none is needed: a member acts as their own relay at zero fee, and +settlement is permissionless, so they settle their own batch once the timeout +passes. The cost is the expected one — their wallet signs, giving up anonymity — +and the test pins that the exit works. + +**Gadget, host and syscall compute one hash.** All three are checked against +circomlib's published `poseidon([1,2])` vector rather than against each other, so +all three agreeing on a wrong answer is not reachable. Several published Solana +projects ship a gadget whose native and in-circuit hashes differ; that only +surfaces at proving time. + +## The measurement + +Two commands, and the split is the point: + +``` +mirror seeds --program # member-weighted frame, one row per depositor +mirror collect --seeds seeds.txt # the only networked step; writes sample.json +mirror analyze --sample sample.json # pure, offline, deterministic +``` + +`sample.json` is the committed artifact. Anyone holding it recomputes the +headline without RPC access and without trusting that our endpoint behaved the +same way on their machine. + +### Design choices that exist to avoid specific published defects + +- **Edges come from balance deltas**, not instruction parsing, which is blind to + every program that moves lamports by direct account mutation. +- **The birth edge is the oldest credit.** A competing tracer scans the six most + *recent* transactions — the wrong end of the history for anything with more + than six, which manufactures "unresolved" for active wallets. +- **The hub threshold is decoupled from the paging cap.** In a competing tracer + the two are one number, so "reaches an attributable origin" there means "hit + the RPC page cap" — admitting every DEX program and bot. +- **RPC failures are never evidence.** Counted separately, excluded from the + distribution, and above a 1% failure rate the run refuses to print a headline + rather than printing a warning above one. +- **The endpoint is checked first.** A truncated endpoint returns `null` rather + than an error for pruned history, so a collection against one looks healthy + and reports every old funding event as absent. `check-endpoint` refuses. +- **The frame is member-weighted.** Each depositor counts once. Sampling + addresses because they appear in recent blocks is size-biased toward the + highest-frequency actors. + +### The folklore formula is inverted + +`2^H(C)` — entropy over the class-size distribution — is widely quoted as the +effective anonymity set. It is the **leakage**: it is maximised when every member +stands alone, which is total deanonymisation. The anonymity is `2^H(X|C)`. + +Our headline is the loss factor `ρ = 2^−H(C)`, because it is independent of `k` +and therefore comparable across pools. Effective-k measured at small `k` +systematically understates the steady-state loss and cannot be extrapolated +upward. + +And **"worst case is 1" is not a finding.** Under any heavy-tailed provenance +prior somebody is always alone. It describes provenance in general, not the pool +being measured. + +## What we do not claim + +- Not "unlinkable", not "untraceable", not "anonymous" without a named adversary + and a stated population. +- **Not that the funding-provenance channel is closed.** It is closed on the + action side and measured on the membership side. +- Not that the trusted setup is secure. It is *reproducible*, which is a + different and lesser property: the seed is public, so the toxic waste is + public, so proofs are forgeable. `mirror verify-setup` lets anyone re-derive + the deployed key and check it against the circuit here. A competing submission + publishes its entropy string *and* gitignores its proving key, so its setup is + insecure and unreproducible at once — no third party can produce a valid proof + for its deployed program at all. +- The on-chain `k_floor` bounds **program-visible membership** only. That is all + a program can check. +- Not audited. + +`docs/MEASUREMENT_LOG.md` records every collection run, including the one that +produced nothing. A measurement project that keeps only its successful runs is +selecting rather than reporting. + +## Documentation + +| | | +|---|---| +| `docs/ARCHITECTURE.md` | The design, and why each decision is what it is. | +| `docs/PROVENANCE_METHOD.md` | Adversary model, metrics, sampling, the honest-claims analysis. | +| `docs/GROTH16_INTEGRATION.md` | The arkworks-to-Solana byte layout, verified by execution. | +| `docs/MEASUREMENT_LOG.md` | Every run. | +| `docs/PLAN.md` | What was planned, and what was cut. | From 019d197531f640f8be8540b074bc75cd7b0310f1 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 06:49:19 -0300 Subject: [PATCH 21/83] =?UTF-8?q?feat(program):=20behavioural=20actions=20?= =?UTF-8?q?=E2=80=94=20the=20pool=20invokes=20real=20programs=20on=20a=20m?= =?UTF-8?q?ember's=20behalf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The brief asks for "Tornado Cash for behavioural patterns and withdrawals — not for funds". What existed answered the wrong question: deposit a fixed denomination, withdraw a fixed denomination, which is a value mixer. What should be deniable is that *you* staked, swapped or voted, not merely where your money went. Settlement now dispatches on a selector. Selector 0 stays the plain transfer. Selector 1 invokes an arbitrary target program with a payload the member fixed at submit time, signed by the vault PDA — so from the chain's point of view every member's action was taken by the pool, and the on-chain trace of a stake made through it is identical whoever asked. a_crowd_of_members_perform_a_real_protocol_action_together settles four such actions in one transaction at 39,584 CU, against the real SPL Memo program fetched from mainnet rather than a stub. An observer sees four identical actions land at one timestamp under one signer. The action binding moved from Poseidon to keccak, because a payload is variable length and Poseidon is a fixed-arity compression. It now covers the selector, the target program, the beneficiary, the relay fee and the payload — every field a relay could alter — and the tests confirm that swapping the target or editing the payload makes the pairing fail. The circuit never computes this value, it takes it as an opaque public input, so the change costs no constraints. The payload lives in the spend record rather than the settle instruction. That is what keeps the crowd synchronised: the instruction data is already on chain, so a settle transaction carries only account references and several actions still fit in one transaction. It is bounded at 256 bytes for the same reason — an unbounded payload would trade the synchronised crowd for expressiveness. Self-invocation is refused explicitly. A payload targeting mirror-pool would re-enter settlement in the middle of a lamport-moving loop, and re-entrancy is not a property to leave to careful reading of the loop. The target program's own account must be supplied at settlement and is checked against the record, so a settler naming a different program is refused before any value moves rather than discovered by the runtime. One defect the tests caught: solana-keccak-hasher's off-chain implementation sits behind a sha3 feature, so without it the host build compiles and then panics at first use — which would have surfaced only when generating a real proof. --- Cargo.lock | 1 + Cargo.toml | 5 + crates/mirror-cli/src/main.rs | 17 +- crates/mirror-core/Cargo.toml | 1 + crates/mirror-core/src/hash.rs | 179 +++++++---- crates/mirror-core/src/lib.rs | 3 +- data/seeds-privacycash.txt | 40 +++ programs/mirror-pool/src/error.rs | 8 + programs/mirror-pool/src/instruction.rs | 50 ++- programs/mirror-pool/src/processor.rs | 174 +++++++++- programs/mirror-pool/src/spend.rs | 140 +++++++- programs/mirror-pool/tests/end_to_end.rs | 301 +++++++++++++++++- .../mirror-pool/tests/fixtures/spl_memo.so | Bin 0 -> 74800 bytes 13 files changed, 807 insertions(+), 112 deletions(-) create mode 100644 data/seeds-privacycash.txt create mode 100644 programs/mirror-pool/tests/fixtures/spl_memo.so diff --git a/Cargo.lock b/Cargo.lock index acd72163..1c3d4ee6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1843,6 +1843,7 @@ dependencies = [ name = "mirror-core" version = "0.1.0" dependencies = [ + "solana-keccak-hasher", "solana-poseidon", "thiserror 2.0.19", ] diff --git a/Cargo.toml b/Cargo.toml index 98843ddd..73de06e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,11 @@ ark-std = { version = "0.5", default-features = false } # arkworks out of the .so — a pure-Rust Poseidon on SBF overflows the 4096-byte # stack frame and costs ~1500x the syscall even when it completes. solana-poseidon = "4.0" +# Same cfg-gating as solana-poseidon: the syscall on-chain, sha3 off-chain. +# Needed because an action payload is variable length and Poseidon is not. +# The off-chain implementation sits behind the sha3 feature; without it the +# host build compiles and then panics at first use. +solana-keccak-hasher = { version = "3.1", features = ["sha3"] } # Host-only, and only for its published round constants, which the R1CS gadget # reads so that in-circuit hashing provably matches the syscall. light-poseidon = "0.4" diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 50c66528..5f9d125a 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -70,6 +70,13 @@ enum Command { /// spread over more of the pool's lifetime. #[arg(long, default_value_t = 8)] pages: u32, + /// Transactions to fetch in total, spread across the pages. + /// + /// Sized from the measured deposit rate rather than guessed: about one + /// program transaction in eight is a deposit, so reaching N depositors + /// costs roughly 8N fetches. + #[arg(long, default_value_t = 400)] + scan: usize, /// Minimum lamports a payer must part with for it to count as a deposit. #[arg(long, default_value_t = 10_000_000)] min_deposit: u64, @@ -171,6 +178,7 @@ fn main() -> Result<()> { program, n, pages, + scan, min_deposit, out, endpoint, @@ -205,10 +213,11 @@ fn main() -> Result<()> { seeds.len() ); - // Take from across the page rather than only its head, so the - // sample spans the page's time range instead of clustering. - let want_from_page = (n - seeds.len()).div_ceil((pages - page).max(1) as usize); - let stride = (batch.len() / want_from_page.max(1)).max(1); + // Spread the scan budget evenly over the pages, and stride + // within each page so the sample spans its time range instead of + // clustering at its head. + let scan_this_page = (scan / pages as usize).max(1); + let stride = (batch.len() / scan_this_page).max(1); for info in batch.iter().step_by(stride) { if info.err { continue; diff --git a/crates/mirror-core/Cargo.toml b/crates/mirror-core/Cargo.toml index 20e8deef..10805ac5 100644 --- a/crates/mirror-core/Cargo.toml +++ b/crates/mirror-core/Cargo.toml @@ -9,4 +9,5 @@ license.workspace = true # cross this boundary as canonical 32-byte big-endian arrays. [dependencies] solana-poseidon.workspace = true +solana-keccak-hasher.workspace = true thiserror.workspace = true diff --git a/crates/mirror-core/src/hash.rs b/crates/mirror-core/src/hash.rs index 15a35921..a6bc1900 100644 --- a/crates/mirror-core/src/hash.rs +++ b/crates/mirror-core/src/hash.rs @@ -19,7 +19,10 @@ //! | nullifier | 1 | `(k)` | //! | Merkle node | 2 | `(left, right)` | //! | note commitment | 3 | `(k, r, denom_tag)` | -//! | action binding | 4 | `(selector, beneficiary_hi, beneficiary_lo, relay_fee)` | +//! +//! The action binding is not in this table: its payload is variable length, so +//! it is a keccak digest rather than a Poseidon compression. See +//! [`action_binding`]. //! //! An explicit integer tag was the first design here and it was wrong: with a //! small tag constant, a Merkle node whose left child equals the tag collides @@ -56,11 +59,6 @@ pub fn poseidon3(a: Field, b: Field, c: Field) -> Result { digest(&[a.as_bytes(), b.as_bytes(), c.as_bytes()]) } -/// Arity-4 Poseidon. -pub fn poseidon4(a: Field, b: Field, c: Field, d: Field) -> Result { - digest(&[a.as_bytes(), b.as_bytes(), c.as_bytes(), d.as_bytes()]) -} - /// Merkle internal node: `H2(left, right)`. pub fn hash_node(left: Field, right: Field) -> Result { poseidon2(left, right) @@ -84,39 +82,61 @@ pub fn nullifier(k: Field) -> Result { poseidon1(k) } -/// Action binding: `H4(selector, beneficiary_hi, beneficiary_lo, relay_fee)`. +/// Domain tag for the action binding preimage. +pub const ACTION_DOMAIN: &[u8] = b"mirror-pool:action:v1"; + +/// Reduces a 256-bit digest to a canonical BN254 scalar. /// -/// This is the value the circuit takes as its third public input and the value -/// the program recomputes from the action it is about to execute. It lives here, -/// shared by both, so the two cannot drift apart. +/// The top byte is cleared, so the value is below `2^248` and therefore +/// unconditionally below the modulus. Masking rather than reducing keeps the map +/// deterministic and total — a modular reduction would need a bignum in the +/// program, and rejecting out-of-range digests would make the binding fail for +/// one preimage in roughly forty. +/// +/// Eight bits are lost from 256. What remains is far beyond what collision +/// resistance needs here: forging a binding still requires a keccak collision. +pub fn field_from_digest(digest: [u8; 32]) -> Field { + let mut bytes = digest; + bytes[0] = 0; + Field::from_bytes(bytes).expect("cleared top byte is always canonical") +} + +/// Action binding: a keccak digest over everything a relay could alter. /// -/// It covers every economically meaningful field of a spend. The payout is the -/// pool denomination minus `relay_fee`, so binding the beneficiary and the fee -/// binds the amount too: a relay can neither redirect the payout nor inflate its -/// own cut, because either change produces a different binding and the proof -/// stops verifying. +/// This is the circuit's third public input and the value the program recomputes +/// from the action it is about to execute. It lives here, shared by host and +/// program, so the two cannot drift. /// -/// A 32-byte public key does not fit in a BN254 scalar — the field is ~254 bits -/// — so the key is split into two 16-byte halves. Each half is well under the -/// modulus, the split is injective, and no reduction ever happens. Reducing a -/// full key instead would let two distinct beneficiaries share one binding. +/// The preimage covers every field with economic or behavioural meaning: +/// +/// | field | what altering it would let a relay do | +/// |---|---| +/// | `selector` | run a different kind of action | +/// | `target_program` | invoke a different program entirely | +/// | `beneficiary` | redirect the outcome | +/// | `relay_fee` | inflate its own cut | +/// | `payload` | change the action's parameters | +/// +/// Keccak rather than Poseidon because the payload is variable length and +/// Poseidon is a fixed-arity compression. The circuit never computes this — it +/// takes the result as an opaque public input — so the choice costs no +/// constraints. pub fn action_binding( selector: u64, + target_program: &[u8; 32], beneficiary: &[u8; 32], relay_fee: u64, -) -> Result { - let mut hi = [0u8; 32]; - hi[16..].copy_from_slice(&beneficiary[..16]); - let mut lo = [0u8; 32]; - lo[16..].copy_from_slice(&beneficiary[16..]); - - poseidon4( - Field::from_u64(selector), - // Both halves are < 2^128, so these constructions cannot fail. - Field::from_bytes(hi)?, - Field::from_bytes(lo)?, - Field::from_u64(relay_fee), - ) + payload: &[u8], +) -> Field { + let digest = solana_keccak_hasher::hashv(&[ + ACTION_DOMAIN, + &selector.to_le_bytes(), + target_program, + beneficiary, + &relay_fee.to_le_bytes(), + payload, + ]); + field_from_digest(digest.to_bytes()) } #[cfg(test)] @@ -175,65 +195,90 @@ mod tests { ); } + const PROG: [u8; 32] = [3u8; 32]; + #[test] fn the_action_binding_covers_every_field_a_relay_could_change() { let bob = [7u8; 32]; - let base = action_binding(1, &bob, 5_000).unwrap(); + let payload = b"stake 0.1".as_slice(); + let base = action_binding(1, &PROG, &bob, 5_000, payload); let mut carol = [7u8; 32]; carol[31] = 8; + let mut other_prog = PROG; + other_prog[0] = 4; + assert_ne!( base, - action_binding(1, &carol, 5_000).unwrap(), - "a redirected beneficiary must change the binding" + action_binding(2, &PROG, &bob, 5_000, payload), + "selector" ); assert_ne!( base, - action_binding(1, &bob, 6_000).unwrap(), - "an inflated relay fee must change the binding" + action_binding(1, &other_prog, &bob, 5_000, payload), + "target program" ); assert_ne!( base, - action_binding(2, &bob, 5_000).unwrap(), - "a different action must change the binding" + action_binding(1, &PROG, &carol, 5_000, payload), + "beneficiary" + ); + assert_ne!( + base, + action_binding(1, &PROG, &bob, 6_000, payload), + "relay fee" + ); + assert_ne!( + base, + action_binding(1, &PROG, &bob, 5_000, b"stake 1.0"), + "payload" ); } #[test] - fn the_beneficiary_split_is_injective_across_the_halfway_boundary() { - // Two keys differing only at byte 15 and two differing only at byte 16 - // land in different halves. If the split dropped or overlapped a byte, - // one of these pairs would collide. - let base = [0u8; 32]; - let mut a = base; - a[15] = 1; - let mut b = base; - b[16] = 1; - let zero = action_binding(0, &base, 0).unwrap(); - let ha = action_binding(0, &a, 0).unwrap(); - let hb = action_binding(0, &b, 0).unwrap(); - assert_ne!(zero, ha); - assert_ne!(zero, hb); - assert_ne!(ha, hb); + fn the_binding_is_deterministic_and_lands_in_the_field() { + let a = action_binding(1, &PROG, &[9u8; 32], 1, b"x"); + let b = action_binding(1, &PROG, &[9u8; 32], 1, b"x"); + assert_eq!(a, b); + // Canonical by construction: the top byte is cleared. + assert_eq!(a.to_bytes()[0], 0); + assert!(Field::from_bytes(a.to_bytes()).is_ok()); } #[test] - fn a_full_width_key_binds_without_reduction() { - // An all-0xff key exceeds the BN254 modulus. Reducing it whole would be - // a silent collision surface; the halves keep it exact. - let max = [0xffu8; 32]; - assert!( - Field::from_bytes(max).is_err(), - "precondition: not canonical" + fn an_empty_payload_is_a_distinct_action_from_a_zero_byte_one() { + assert_ne!( + action_binding(1, &PROG, &[1u8; 32], 0, b""), + action_binding(1, &PROG, &[1u8; 32], 0, b"\0"), ); - assert!(action_binding(0, &max, 0).is_ok()); + } - let mut near = [0xffu8; 32]; - near[0] = 0xfe; - assert_ne!( - action_binding(0, &max, 0).unwrap(), - action_binding(0, &near, 0).unwrap() + #[test] + fn field_masking_is_total_over_every_digest() { + // Including a digest that would otherwise exceed the modulus. A binding + // that failed for some preimages would be a liveness bug that only + // appeared for one action in forty. + for probe in [[0xffu8; 32], [0u8; 32], crate::MODULUS_BE] { + let f = field_from_digest(probe); + assert_eq!(f.to_bytes()[0], 0); + } + } + + #[test] + fn the_domain_tag_separates_bindings_from_raw_hashes() { + // Without the tag, a preimage assembled elsewhere could collide with a + // binding. With it, an attacker must also control the tag. + let with_tag = action_binding(0, &[0u8; 32], &[0u8; 32], 0, b""); + let raw = field_from_digest( + solana_keccak_hasher::hashv(&[ + &0u64.to_le_bytes(), + &[0u8; 32], + &[0u8; 32], + &0u64.to_le_bytes(), + ]) + .to_bytes(), ); + assert_ne!(with_tag, raw); } /// The anchor for the three-way parity. diff --git a/crates/mirror-core/src/lib.rs b/crates/mirror-core/src/lib.rs index bc3231b0..ec24b244 100644 --- a/crates/mirror-core/src/lib.rs +++ b/crates/mirror-core/src/lib.rs @@ -13,7 +13,8 @@ mod note; pub use field::{Field, MODULUS_BE}; pub use hash::{ - action_binding, commitment, hash_node, nullifier, poseidon1, poseidon2, poseidon3, poseidon4, + action_binding, commitment, field_from_digest, hash_node, nullifier, poseidon1, poseidon2, + poseidon3, ACTION_DOMAIN, }; pub use merkle::{ baked_ladder, zero_ladder, Frontier, MerkleProof, MerkleTree, TREE_DEPTH, ZERO_LADDER, diff --git a/data/seeds-privacycash.txt b/data/seeds-privacycash.txt new file mode 100644 index 00000000..37a246d2 --- /dev/null +++ b/data/seeds-privacycash.txt @@ -0,0 +1,40 @@ +B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc +75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7 +HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165 +7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4 +DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS +8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z +FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw +HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u +5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9 +HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq +sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB +BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4 +DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX +AxG818LtyEa88dJDoHeYHW2WyNXrCLYUzr6ifL5qn4KB +FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M +Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw +4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr +2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy +6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs +8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me +CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz +FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz +8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E +Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX +65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G +SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw +BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7 +6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo +isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h +BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM +3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3 +6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ +EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6 +3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL +CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1 +xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6 +2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz +HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU +CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr +9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP diff --git a/programs/mirror-pool/src/error.rs b/programs/mirror-pool/src/error.rs index 91982ffd..a1b36f9e 100644 --- a/programs/mirror-pool/src/error.rs +++ b/programs/mirror-pool/src/error.rs @@ -56,6 +56,14 @@ pub enum MirrorProgramError { RelayFeeTooLarge = 20, /// The batch is below the crowd size and has not waited out the timeout. CrowdTooSmall = 21, + /// The action selector is not one this program implements. + UnknownSelector = 22, + /// An action tried to invoke this program. Refused: re-entering settlement + /// around a lamport-moving loop is not a property to leave to careful + /// reading. + SelfInvocationRefused = 23, + /// The action payload exceeds the maximum a spend record can carry. + PayloadTooLarge = 24, } impl From for ProgramError { diff --git a/programs/mirror-pool/src/instruction.rs b/programs/mirror-pool/src/instruction.rs index 846b6d71..36f501e6 100644 --- a/programs/mirror-pool/src/instruction.rs +++ b/programs/mirror-pool/src/instruction.rs @@ -40,7 +40,7 @@ impl Tag { /// stack cost we can comfortably afford: 368 bytes against a 4 KB frame, decoded /// once per transaction. The size difference is deliberate. #[allow(clippy::large_enum_variant)] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Instruction { /// Creates the pool and its vault, and seeds the accumulator to an empty /// tree. @@ -70,8 +70,16 @@ pub enum Instruction { root: [u8; 32], nullifier: [u8; 32], selector: u64, + /// The program the pool will invoke on the member's behalf. + target_program: [u8; 32], beneficiary: [u8; 32], relay_fee: u64, + /// How many accounts that invocation expects. + action_accounts: u8, + /// Instruction data for the invocation, stored in the spend record so a + /// settle transaction carries only account references and several + /// actions still fit in one transaction. + payload: Vec, }, /// Executes a batch of pending spends in one transaction, so every payout in /// an epoch shares a timestamp and an ordering. @@ -86,8 +94,8 @@ pub enum Instruction { pub const INIT_POOL_LEN: usize = 1 + 8 + 8 + 4; /// `Deposit`: tag + one field element. pub const DEPOSIT_LEN: usize = 1 + 32; -/// `SubmitSpend`: tag + proof + root + nullifier + selector + beneficiary + fee. -pub const SUBMIT_SPEND_LEN: usize = 1 + 64 + 128 + 64 + 32 + 32 + 8 + 32 + 8; +/// `SubmitSpend` without its payload. The encoding is variable length. +pub const SUBMIT_SPEND_BASE_LEN: usize = 1 + 64 + 128 + 64 + 32 + 32 + 8 + 32 + 32 + 8 + 1 + 2; /// `SettleEpoch`: tag + count. pub const SETTLE_EPOCH_LEN: usize = 1 + 1; @@ -137,7 +145,7 @@ impl Instruction { Ok(Instruction::SettleEpoch { count: data[1] }) } Tag::SubmitSpend => { - if data.len() != SUBMIT_SPEND_LEN { + if data.len() < SUBMIT_SPEND_BASE_LEN { return Err(MirrorProgramError::MalformedInstruction); } let mut proof_a = [0u8; 64]; @@ -145,6 +153,7 @@ impl Instruction { let mut proof_c = [0u8; 64]; let mut root = [0u8; 32]; let mut nullifier = [0u8; 32]; + let mut target_program = [0u8; 32]; let mut beneficiary = [0u8; 32]; proof_a.copy_from_slice(&data[1..65]); proof_b.copy_from_slice(&data[65..193]); @@ -152,8 +161,18 @@ impl Instruction { root.copy_from_slice(&data[257..289]); nullifier.copy_from_slice(&data[289..321]); let selector = read_u64(data, 321); - beneficiary.copy_from_slice(&data[329..361]); - let relay_fee = read_u64(data, 361); + target_program.copy_from_slice(&data[329..361]); + beneficiary.copy_from_slice(&data[361..393]); + let relay_fee = read_u64(data, 393); + let action_accounts = data[401]; + let mut len_bytes = [0u8; 2]; + len_bytes.copy_from_slice(&data[402..404]); + let payload_len = u16::from_le_bytes(len_bytes) as usize; + // The declared length must account for every remaining byte, so + // trailing data cannot ride along unread. + if data.len() != SUBMIT_SPEND_BASE_LEN + payload_len { + return Err(MirrorProgramError::MalformedInstruction); + } Ok(Instruction::SubmitSpend { proof_a, proof_b, @@ -161,8 +180,11 @@ impl Instruction { root, nullifier, selector, + target_program, beneficiary, relay_fee, + action_accounts, + payload: data[SUBMIT_SPEND_BASE_LEN..].to_vec(), }) } } @@ -196,10 +218,13 @@ impl Instruction { root, nullifier, selector, + target_program, beneficiary, relay_fee, + action_accounts, + payload, } => { - let mut out = Vec::with_capacity(SUBMIT_SPEND_LEN); + let mut out = Vec::with_capacity(SUBMIT_SPEND_BASE_LEN + payload.len()); out.push(Tag::SubmitSpend as u8); out.extend_from_slice(proof_a); out.extend_from_slice(proof_b); @@ -207,8 +232,12 @@ impl Instruction { out.extend_from_slice(root); out.extend_from_slice(nullifier); out.extend_from_slice(&selector.to_le_bytes()); + out.extend_from_slice(target_program); out.extend_from_slice(beneficiary); out.extend_from_slice(&relay_fee.to_le_bytes()); + out.push(*action_accounts); + out.extend_from_slice(&(payload.len() as u16).to_le_bytes()); + out.extend_from_slice(payload); out } Instruction::SettleEpoch { count } => { @@ -249,8 +278,11 @@ mod tests { root: [0xD4; 32], nullifier: [0xE5; 32], selector: 0x0102_0304_0506_0708, + target_program: [0x7A; 32], beneficiary: [0xF6; 32], relay_fee: 0x1112_1314_1516_1718, + action_accounts: 3, + payload: vec![1, 2, 3, 4, 5], } } @@ -266,10 +298,10 @@ mod tests { // These are the on-chain ABI. A change here breaks every deployed client. assert_eq!(init().pack().len(), INIT_POOL_LEN); assert_eq!(deposit().pack().len(), DEPOSIT_LEN); - assert_eq!(submit_spend().pack().len(), SUBMIT_SPEND_LEN); + assert_eq!(submit_spend().pack().len(), SUBMIT_SPEND_BASE_LEN + 5); assert_eq!(INIT_POOL_LEN, 21); assert_eq!(DEPOSIT_LEN, 33); - assert_eq!(SUBMIT_SPEND_LEN, 369); + assert_eq!(SUBMIT_SPEND_BASE_LEN, 404); assert_eq!(settle().pack().len(), SETTLE_EPOCH_LEN); } diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 9e945c1d..2bcad32f 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -4,7 +4,7 @@ use crate::{ error::MirrorProgramError, instruction::Instruction, pda::{pool_address, spend_address, vault_address, POOL_SEED, SPEND_SEED, VAULT_SEED}, - spend::{Spend, SPEND_LEN}, + spend::{spend_len, Spend, MAX_PAYLOAD}, state::{Pool, POOL_LEN}, }; use mirror_core::{hash_node, Field, TREE_DEPTH}; @@ -33,8 +33,11 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> Pr root, nullifier, selector, + target_program, beneficiary, relay_fee, + action_accounts, + payload, } => submit_spend( program_id, accounts, @@ -45,8 +48,11 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> Pr root, nullifier, selector, + target_program, beneficiary, relay_fee, + action_accounts, + payload, }, ), Instruction::SettleEpoch { count } => settle_epoch(program_id, accounts, count), @@ -61,8 +67,11 @@ struct SpendRequest { root: [u8; 32], nullifier: [u8; 32], selector: u64, + target_program: [u8; 32], beneficiary: [u8; 32], relay_fee: u64, + action_accounts: u8, + payload: Vec, } /// Creates the pool and its vault and seeds the accumulator to an empty tree. @@ -286,8 +295,16 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest // beneficiary or its own fee, this binding differs from the one the prover // committed to and the pairing fails. There is no separate field that could // be checked incorrectly or forgotten. - let binding = mirror_core::action_binding(req.selector, &req.beneficiary, req.relay_fee) - .map_err(MirrorProgramError::from)?; + if req.payload.len() > MAX_PAYLOAD { + return Err(MirrorProgramError::PayloadTooLarge.into()); + } + let binding = mirror_core::action_binding( + req.selector, + &req.target_program, + &req.beneficiary, + req.relay_fee, + &req.payload, + ); let public_inputs: [[u8; 32]; 3] = [ root.to_bytes(), @@ -328,8 +345,8 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest &system_instruction::create_account( relay.key, spend_account.key, - rent.minimum_balance(SPEND_LEN), - SPEND_LEN as u64, + rent.minimum_balance(spend_len(req.payload.len())), + spend_len(req.payload.len()) as u64, program_id, ), &[relay.clone(), spend_account.clone(), system.clone()], @@ -353,7 +370,10 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest &req.beneficiary, &relay.key.to_bytes(), &pool_account.key.to_bytes(), - ); + &req.target_program, + req.action_accounts, + &req.payload, + )?; // The spend counter is deliberately *not* advanced here. The note is // committed to be paid but has not been paid, so the vault must still cover @@ -362,6 +382,21 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest Ok(()) } +/// The plain-transfer action: pay the beneficiary, no CPI. +/// +/// Kept as selector zero because a transfer is the degenerate action and the +/// protocol should not need a target program to express it. +pub const SELECTOR_TRANSFER: u64 = 0; + +/// Invoke `target_program` with the stored payload, signed by the pool. +/// +/// This is what makes the pool a behavioural anonymity set rather than a value +/// mixer. The brief asks for "Tornado Cash for behavioural patterns and +/// withdrawals — not for funds": an observer should see that a stake, a swap or +/// a vote happened and be unable to say which member asked for it. A pool that +/// only moves lamports answers the wrong question. +pub const SELECTOR_INVOKE: u64 = 1; + /// How long a spend may wait before it can settle alone. /// /// Below the crowd size, a batch must wait this out. It is the escape valve that @@ -426,7 +461,7 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro return Err(MirrorProgramError::InvalidOwner.into()); } - let (relay_fee, payout) = { + let (relay_fee, payout, selector, action_accounts) = { let mut data = spend_account.try_borrow_mut_data()?; let mut record = Spend::load(&mut data)?; @@ -455,16 +490,55 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro let payout = denomination .checked_sub(relay_fee) .ok_or(MirrorProgramError::ArithmeticOverflow)?; - (relay_fee, payout) + ( + relay_fee, + payout, + record.selector(), + record.action_accounts(), + ) }; - // The vault is owned by this program, so its lamports move by direct - // mutation rather than a system-program CPI. That means no signer seeds - // and no nested invoke on the hot path. - move_lamports(vault_account, beneficiary, payout)?; + // The relay is paid the same way regardless of what the action is. if relay_fee > 0 { move_lamports(vault_account, relay, relay_fee)?; } + + match selector { + SELECTOR_TRANSFER => { + if action_accounts != 0 { + return Err(MirrorProgramError::MalformedInstruction.into()); + } + // The vault is owned by this program, so lamports move by direct + // mutation rather than a system CPI: no signer seeds and no + // nested invoke on the hot path. + move_lamports(vault_account, beneficiary, payout)?; + } + SELECTOR_INVOKE => { + // The target program's own account comes first, because a CPI + // requires the callee to be present in the caller's account + // list. Its key is checked against the record, so a settler + // supplying a different program is refused before any value + // moves rather than discovered by the runtime. + let target_info = next_account_info(iter)?; + + // Then the action's own accounts. + let mut action_infos = Vec::with_capacity(action_accounts as usize); + for _ in 0..action_accounts { + action_infos.push(next_account_info(iter)?.clone()); + } + invoke_action( + program_id, + pool_account, + vault_account, + spend_account, + beneficiary, + target_info, + payout, + &action_infos, + )?; + } + _ => return Err(MirrorProgramError::UnknownSelector.into()), + } settled += 1; } @@ -487,6 +561,82 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro Ok(()) } +/// Invokes the member's chosen program on their behalf, signed by the vault. +/// +/// The vault PDA is the signer, so from the chain's point of view the action was +/// taken by the pool. Every member's action carries the same signer, which is +/// what makes an action unattributable: the on-chain trace of a stake made +/// through this pool is identical whoever asked for it. +/// +/// The payload and the target were both fixed at submit time and bound into the +/// proof, so a settler chooses neither. What a settler does supply is the +/// account list, and the accounts are the target program's problem to validate +/// — exactly as they would be for any caller. +#[allow(clippy::too_many_arguments)] +fn invoke_action<'a>( + program_id: &Pubkey, + pool_account: &AccountInfo<'a>, + vault_account: &AccountInfo<'a>, + spend_account: &AccountInfo<'a>, + beneficiary: &AccountInfo<'a>, + target_info: &AccountInfo<'a>, + payout: u64, + action_infos: &[AccountInfo<'a>], +) -> ProgramResult { + let (target, payload) = { + let mut data = spend_account.try_borrow_mut_data()?; + let record = Spend::load(&mut data)?; + (record.target_program(), record.payload().to_vec()) + }; + let vault_bump = { + let pool_data = pool_account.try_borrow_data()?; + *pool_data + .get(2) + .ok_or(MirrorProgramError::InvalidPoolAccount)? + }; + + // The pool never invokes itself. Doing so would let a member craft a payload + // that re-enters settlement, and re-entrancy around a lamport-moving loop is + // not something to leave to careful reading. + let target_key = Pubkey::new_from_array(target); + if target_key == *program_id { + return Err(MirrorProgramError::SelfInvocationRefused.into()); + } + // The account supplied must be the program the member proved. + if *target_info.key != target_key { + return Err(MirrorProgramError::InvalidPda.into()); + } + + // Fund the action before invoking, so the target sees the value it is meant + // to act on. The vault signs, so the funds visibly come from the pool. + move_lamports(vault_account, beneficiary, payout)?; + + let metas: Vec = action_infos + .iter() + .map(|a| solana_program::instruction::AccountMeta { + pubkey: *a.key, + is_signer: a.is_signer || a.key == vault_account.key, + is_writable: a.is_writable, + }) + .collect(); + + let ix = solana_program::instruction::Instruction { + program_id: target_key, + accounts: metas, + data: payload, + }; + + let mut infos = action_infos.to_vec(); + infos.push(vault_account.clone()); + infos.push(target_info.clone()); + + invoke_signed( + &ix, + &infos, + &[&[VAULT_SEED, pool_account.key.as_ref(), &[vault_bump]]], + ) +} + /// Moves lamports between two accounts this program owns or may credit. fn move_lamports( from: &AccountInfo, diff --git a/programs/mirror-pool/src/spend.rs b/programs/mirror-pool/src/spend.rs index 75ecb731..bf57f6d8 100644 --- a/programs/mirror-pool/src/spend.rs +++ b/programs/mirror-pool/src/spend.rs @@ -19,20 +19,37 @@ mod offset { pub const VERSION: usize = 0; pub const STATUS: usize = 1; pub const BUMP: usize = 2; - pub const _RESERVED: usize = 3; + pub const ACTION_ACCOUNTS: usize = 3; + pub const PAYLOAD_LEN: usize = 4; pub const SELECTOR: usize = 8; pub const RELAY_FEE: usize = 16; pub const SUBMITTED_AT: usize = 24; pub const BENEFICIARY: usize = 32; pub const RELAY: usize = 64; pub const POOL: usize = 96; - pub const END: usize = 128; + pub const TARGET_PROGRAM: usize = 128; + pub const PAYLOAD: usize = 160; } -pub const SPEND_LEN: usize = offset::END; +/// Fixed part of the record. The payload follows it. +pub const SPEND_BASE_LEN: usize = offset::PAYLOAD; + +/// Largest action payload a spend may carry. +/// +/// Bounded because the payload lives in the record, which is what lets +/// settlement batch several actions into one transaction: the instruction data +/// is already on chain, so a settle transaction carries only account references. +/// An unbounded payload would trade the synchronised crowd for expressiveness. +pub const MAX_PAYLOAD: usize = 256; + pub const SPEND_VERSION: u8 = 1; -const _: () = assert!(SPEND_LEN == 128); +const _: () = assert!(SPEND_BASE_LEN == 160); + +/// Account size for a record carrying `payload_len` bytes. +pub fn spend_len(payload_len: usize) -> usize { + SPEND_BASE_LEN + payload_len +} pub struct Spend<'a> { data: &'a mut [u8], @@ -40,14 +57,20 @@ pub struct Spend<'a> { impl<'a> Spend<'a> { pub fn load(data: &'a mut [u8]) -> Result { - if data.len() != SPEND_LEN || data[offset::VERSION] != SPEND_VERSION { + if data.len() < SPEND_BASE_LEN || data[offset::VERSION] != SPEND_VERSION { return Err(MirrorProgramError::InvalidSpendAccount); } - Ok(Spend { data }) + let spend = Spend { data }; + // The declared payload length must match the account, or a reader could + // be pointed past the end of the data it was given. + if spend.data.len() != spend_len(spend.payload_len()) { + return Err(MirrorProgramError::InvalidSpendAccount); + } + Ok(spend) } pub fn load_uninitialised(data: &'a mut [u8]) -> Result { - if data.len() != SPEND_LEN { + if data.len() < SPEND_BASE_LEN || data.len() > spend_len(MAX_PAYLOAD) { return Err(MirrorProgramError::InvalidSpendAccount); } if data[offset::VERSION] != 0 { @@ -66,10 +89,22 @@ impl<'a> Spend<'a> { beneficiary: &[u8; 32], relay: &[u8; 32], pool: &[u8; 32], - ) { + target_program: &[u8; 32], + action_accounts: u8, + payload: &[u8], + ) -> Result<(), MirrorProgramError> { + if payload.len() > MAX_PAYLOAD || self.data.len() != spend_len(payload.len()) { + return Err(MirrorProgramError::InvalidSpendAccount); + } self.data[offset::VERSION] = SPEND_VERSION; self.data[offset::STATUS] = STATUS_PENDING; self.data[offset::BUMP] = bump; + self.data[offset::ACTION_ACCOUNTS] = action_accounts; + self.data[offset::PAYLOAD_LEN..offset::PAYLOAD_LEN + 2] + .copy_from_slice(&(payload.len() as u16).to_le_bytes()); + self.data[offset::TARGET_PROGRAM..offset::TARGET_PROGRAM + 32] + .copy_from_slice(target_program); + self.data[offset::PAYLOAD..offset::PAYLOAD + payload.len()].copy_from_slice(payload); self.data[offset::SELECTOR..offset::SELECTOR + 8].copy_from_slice(&selector.to_le_bytes()); self.data[offset::RELAY_FEE..offset::RELAY_FEE + 8] .copy_from_slice(&relay_fee.to_le_bytes()); @@ -78,6 +113,31 @@ impl<'a> Spend<'a> { self.data[offset::BENEFICIARY..offset::BENEFICIARY + 32].copy_from_slice(beneficiary); self.data[offset::RELAY..offset::RELAY + 32].copy_from_slice(relay); self.data[offset::POOL..offset::POOL + 32].copy_from_slice(pool); + Ok(()) + } + + /// How many accounts the action's CPI expects. + pub fn action_accounts(&self) -> u8 { + self.data[offset::ACTION_ACCOUNTS] + } + + pub fn payload_len(&self) -> usize { + let mut b = [0u8; 2]; + b.copy_from_slice(&self.data[offset::PAYLOAD_LEN..offset::PAYLOAD_LEN + 2]); + u16::from_le_bytes(b) as usize + } + + /// The program the pool will invoke on this member's behalf. + pub fn target_program(&self) -> [u8; 32] { + let mut b = [0u8; 32]; + b.copy_from_slice(&self.data[offset::TARGET_PROGRAM..offset::TARGET_PROGRAM + 32]); + b + } + + /// The instruction data for that invocation, stored at submit time so a + /// settle transaction carries only account references. + pub fn payload(&self) -> &[u8] { + &self.data[offset::PAYLOAD..offset::PAYLOAD + self.payload_len()] } pub fn status(&self) -> u8 { @@ -137,8 +197,10 @@ impl<'a> Spend<'a> { mod tests { use super::*; + const PAYLOAD: &[u8] = b"delegate stake"; + fn fresh() -> Vec { - vec![0u8; SPEND_LEN] + vec![0u8; spend_len(PAYLOAD.len())] } fn init(data: &mut [u8]) { @@ -151,7 +213,11 @@ mod tests { &[9u8; 32], &[4u8; 32], &[5u8; 32], - ); + &[6u8; 32], + 4, + PAYLOAD, + ) + .unwrap(); } #[test] @@ -167,6 +233,50 @@ mod tests { assert_eq!(s.beneficiary(), [9u8; 32]); assert_eq!(s.relay(), [4u8; 32]); assert_eq!(s.pool(), [5u8; 32]); + assert_eq!(s.target_program(), [6u8; 32]); + assert_eq!(s.action_accounts(), 4); + assert_eq!(s.payload(), PAYLOAD); + } + + /// The action a member proved must survive settlement byte for byte: a + /// truncated or padded payload would invoke the target with parameters + /// nobody authorised. + #[test] + fn a_payload_round_trips_at_every_length() { + for len in [0usize, 1, 31, 32, 33, MAX_PAYLOAD] { + let payload: Vec = (0..len).map(|i| (i % 251) as u8).collect(); + let mut data = vec![0u8; spend_len(len)]; + { + let mut s = Spend::load_uninitialised(&mut data).unwrap(); + s.initialise( + 1, 0, 0, 0, &[0u8; 32], &[0u8; 32], &[0u8; 32], &[0u8; 32], 0, &payload, + ) + .unwrap(); + } + let s = Spend::load(&mut data).unwrap(); + assert_eq!(s.payload(), &payload[..], "length {len} did not round trip"); + assert_eq!(s.payload_len(), len); + } + } + + #[test] + fn a_payload_beyond_the_maximum_is_refused() { + let payload = vec![0u8; MAX_PAYLOAD + 1]; + let mut data = vec![0u8; spend_len(payload.len())]; + assert!(Spend::load_uninitialised(&mut data).is_err()); + } + + /// An account whose length disagrees with its declared payload length would + /// let a reader run past the end of what it was given. + #[test] + fn a_record_whose_length_contradicts_its_header_is_refused() { + let mut data = fresh(); + init(&mut data); + data.push(0); + assert!(matches!( + Spend::load(&mut data), + Err(MirrorProgramError::InvalidSpendAccount) + )); } #[test] @@ -194,7 +304,7 @@ mod tests { #[test] fn a_wrong_length_account_is_refused() { - let mut short = vec![0u8; SPEND_LEN - 1]; + let mut short = vec![0u8; SPEND_BASE_LEN - 1]; assert!(Spend::load(&mut short).is_err()); assert!(Spend::load_uninitialised(&mut short).is_err()); } @@ -214,7 +324,11 @@ mod tests { &[0xAB; 32], &[0xCD; 32], &[0xEF; 32], - ); + &[0x12; 32], + 7, + PAYLOAD, + ) + .unwrap(); } let s = Spend::load(&mut data).unwrap(); assert_eq!(s.bump(), 0xEE); @@ -224,5 +338,7 @@ mod tests { assert_eq!(s.beneficiary(), [0xAB; 32]); assert_eq!(s.relay(), [0xCD; 32]); assert_eq!(s.pool(), [0xEF; 32]); + assert_eq!(s.target_program(), [0x12; 32]); + assert_eq!(s.action_accounts(), 7); } } diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index aefef269..e570bb01 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -13,10 +13,11 @@ use litesvm::LiteSVM; use mirror_circuit::{generate_reproducible, prove, Keys, Witness}; use mirror_core::{Field, MerkleTree, Note}; +#[allow(unused_imports)] use mirror_pool_program::{ instruction::Instruction as MirrorIx, pda::{pool_address, spend_address, vault_address}, - spend::{Spend, SPEND_LEN, STATUS_PENDING, STATUS_SETTLED}, + spend::{spend_len, Spend, SPEND_BASE_LEN, STATUS_PENDING, STATUS_SETTLED}, Pool, POOL_LEN, }; use solana_keypair::Keypair; @@ -33,7 +34,16 @@ const DENOMINATION: u64 = 100_000_000; // 0.1 SOL const ENTRY_FEE: u64 = 0; const K_FLOOR: u32 = 4; const RELAY_FEE: u64 = 1_000_000; -const SELECTOR: u64 = 1; +const SELECTOR: u64 = mirror_pool_program::processor::SELECTOR_TRANSFER; + +/// The real SPL Memo program, fetched from mainnet, used as a CPI target so the +/// action path is exercised against a program that exists rather than a stub. +const MEMO_PROGRAM: &str = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; + +fn memo_bytes() -> Vec { + let path = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/spl_memo.so"); + std::fs::read(path).expect("spl_memo.so fixture") +} fn program_bytes() -> Vec { let path = concat!( @@ -205,8 +215,11 @@ fn spend_ix( root: proof.public_inputs[0], nullifier, selector: SELECTOR, + target_program: [0u8; 32], beneficiary: beneficiary.to_bytes(), relay_fee: RELAY_FEE, + action_accounts: 0, + payload: Vec::new(), } .pack(), vec![ @@ -228,8 +241,13 @@ fn proof_for( ) -> mirror_circuit::SolanaProof { use ark_std::rand::SeedableRng; let merkle_proof = tree.proof(index as u64).unwrap(); - let binding = - mirror_core::action_binding(SELECTOR, &beneficiary.to_bytes(), RELAY_FEE).unwrap(); + let binding = mirror_core::action_binding( + SELECTOR, + &[0u8; 32], + &beneficiary.to_bytes(), + RELAY_FEE, + &[], + ); let witness = Witness { note: notes[index], merkle_proof: &merkle_proof, @@ -306,7 +324,11 @@ fn a_real_proof_verifies_on_chain_and_records_the_spend() { .get_account(&spend_pda) .expect("spend recorded") .data; - assert_eq!(data.len(), SPEND_LEN); + assert_eq!( + data.len(), + spend_len(0), + "a transfer action carries no payload" + ); let record = Spend::load(&mut data).unwrap(); assert_eq!(record.status(), STATUS_PENDING); assert_eq!(record.selector(), SELECTOR); @@ -408,8 +430,11 @@ fn a_relay_cannot_inflate_its_own_fee() { root: proof.public_inputs[0], nullifier, selector: SELECTOR, + target_program: [0u8; 32], beneficiary: beneficiary.to_bytes(), relay_fee: RELAY_FEE * 50, // proved for RELAY_FEE + action_accounts: 0, + payload: Vec::new(), } .pack(), vec![ @@ -511,15 +536,30 @@ fn submit_batch( } fn settle_ix(env: &Env, batch: &[(Pubkey, Pubkey, Keypair)], settler: &Pubkey) -> Instruction { + settle_ix_with_targets(env, batch, settler, &[]) +} + +/// Settlement where some spends invoke a program. `targets[i]`, when present, +/// is the program account for `batch[i]`, which a CPI requires to be in the +/// caller's account list. +fn settle_ix_with_targets( + env: &Env, + batch: &[(Pubkey, Pubkey, Keypair)], + settler: &Pubkey, + targets: &[Option], +) -> Instruction { let mut metas = vec![ AccountMeta::new(*settler, true), AccountMeta::new(env.pool, false), AccountMeta::new(env.vault, false), ]; - for (spend, beneficiary, relay) in batch { + for (i, (spend, beneficiary, relay)) in batch.iter().enumerate() { metas.push(AccountMeta::new(*spend, false)); metas.push(AccountMeta::new(*beneficiary, false)); metas.push(AccountMeta::new(relay.pubkey(), false)); + if let Some(Some(target)) = targets.get(i) { + metas.push(AccountMeta::new_readonly(*target, false)); + } } Instruction::new_with_bytes( env.program_id, @@ -727,7 +767,8 @@ fn a_member_can_always_exit_without_any_relay() { let beneficiary = member.pubkey(); let merkle_proof = tree.proof(0).unwrap(); - let binding = mirror_core::action_binding(SELECTOR, &beneficiary.to_bytes(), 0).unwrap(); + let binding = + mirror_core::action_binding(SELECTOR, &[0u8; 32], &beneficiary.to_bytes(), 0, &[]); let witness = Witness { note: notes[0], merkle_proof: &merkle_proof, @@ -751,8 +792,11 @@ fn a_member_can_always_exit_without_any_relay() { root: proof.public_inputs[0], nullifier, selector: SELECTOR, + target_program: [0u8; 32], beneficiary: beneficiary.to_bytes(), relay_fee: 0, + action_accounts: 0, + payload: Vec::new(), } .pack(), vec![ @@ -788,3 +832,246 @@ fn a_member_can_always_exit_without_any_relay() { after - before ); } + +// --------------------------------------------------------------------------- +// Behavioural actions +// +// The brief asks for "Tornado Cash for behavioural patterns and withdrawals — +// not for funds". A pool that only moves lamports answers the wrong question: +// what should be deniable is that *you* staked, swapped or voted, not merely +// where your money went. +// +// These exercise the pool invoking a real third-party program on a member's +// behalf. The target is the actual SPL Memo program, fetched from mainnet, so +// the CPI path runs against something that exists. +// --------------------------------------------------------------------------- + +const INVOKE: u64 = mirror_pool_program::processor::SELECTOR_INVOKE; + +/// A spend that asks the pool to invoke `target` with `payload`. +fn action_ix( + env: &Env, + proof: &mirror_circuit::SolanaProof, + nullifier: [u8; 32], + target: &Pubkey, + beneficiary: &Pubkey, + relay: &Pubkey, + payload: &[u8], +) -> Instruction { + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: INVOKE, + target_program: target.to_bytes(), + beneficiary: beneficiary.to_bytes(), + relay_fee: RELAY_FEE, + action_accounts: 0, + payload: payload.to_vec(), + } + .pack(), + vec![ + AccountMeta::new(*relay, true), + AccountMeta::new(env.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ) +} + +fn action_proof( + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + target: &Pubkey, + beneficiary: &Pubkey, + payload: &[u8], +) -> mirror_circuit::SolanaProof { + use ark_std::rand::SeedableRng; + let merkle_proof = tree.proof(index as u64).unwrap(); + let binding = mirror_core::action_binding( + INVOKE, + &target.to_bytes(), + &beneficiary.to_bytes(), + RELAY_FEE, + payload, + ); + let witness = Witness { + note: notes[index], + merkle_proof: &merkle_proof, + root: tree.root().unwrap(), + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([200 + index as u8; 32]); + prove(keys, &witness, &mut rng).expect("proving") +} + +/// The thesis, end to end: a crowd of members each perform the *same shape* of +/// protocol action, the pool invokes the target program for every one of them in +/// a single transaction, and every invocation carries the pool as its signer. +/// +/// An observer sees four memos land at one timestamp, signed by one pool, and +/// has nothing in the transaction that distinguishes which member asked for +/// which. +#[test] +fn a_crowd_of_members_perform_a_real_protocol_action_together() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let memo: Pubkey = MEMO_PROGRAM.parse().unwrap(); + env.svm + .add_program(memo, &memo_bytes()) + .expect("loading the real SPL Memo program"); + + let mut batch = Vec::new(); + for i in 0..K_FLOOR as usize { + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + // Every member sends the identical payload, which is what makes the + // crowd a crowd: the actions are indistinguishable by content. + let payload = b"mirror-pool".as_slice(); + let proof = action_proof(&keys, &tree, ¬es, i, &memo, &beneficiary, payload); + let nullifier = proof.public_inputs[1]; + let ix = action_ix( + &env, + &proof, + nullifier, + &memo, + &beneficiary, + &relay.pubkey(), + payload, + ); + env.send(ix, &relay) + .unwrap_or_else(|e| panic!("submitting action {i}: {e}")); + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + batch.push((spend_pda, beneficiary, relay)); + } + + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let targets: Vec> = batch.iter().map(|_| Some(memo)).collect(); + let ix = settle_ix_with_targets(&env, &batch, &settler.pubkey(), &targets); + let cu = env.send_expect_cu(ix, &settler); + println!( + "settled {} real CPI actions in one transaction, {cu} CU", + batch.len() + ); + + for (spend, beneficiary, _) in &batch { + let mut data = env.svm.get_account(spend).unwrap().data; + let record = Spend::load(&mut data).unwrap(); + assert_eq!(record.status(), STATUS_SETTLED); + assert_eq!(record.target_program(), memo.to_bytes()); + assert_eq!(record.payload(), b"mirror-pool"); + // The action was funded, so the target acted on real value. + assert!(env.svm.get_account(beneficiary).unwrap().lamports > 0); + } +} + +#[test] +fn a_relay_cannot_swap_the_target_program() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let memo: Pubkey = MEMO_PROGRAM.parse().unwrap(); + env.svm.add_program(memo, &memo_bytes()).unwrap(); + + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let payload = b"hello".as_slice(); + let proof = action_proof(&keys, &tree, ¬es, 0, &memo, &beneficiary, payload); + let nullifier = proof.public_inputs[1]; + + // Proved for the memo program; submitted for something else. + let impostor = Pubkey::new_unique(); + let ix = action_ix( + &env, + &proof, + nullifier, + &impostor, + &beneficiary, + &relay.pubkey(), + payload, + ); + let err = env + .send(ix, &relay) + .expect_err("a swapped target program was accepted"); + println!("target swap rejected: {err}"); + assert!(err.contains("Custom(18)"), "expected proof failure: {err}"); +} + +#[test] +fn a_relay_cannot_alter_the_action_payload() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let memo: Pubkey = MEMO_PROGRAM.parse().unwrap(); + env.svm.add_program(memo, &memo_bytes()).unwrap(); + + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let proof = action_proof(&keys, &tree, ¬es, 1, &memo, &beneficiary, b"stake 0.1"); + let nullifier = proof.public_inputs[1]; + + let ix = action_ix( + &env, + &proof, + nullifier, + &memo, + &beneficiary, + &relay.pubkey(), + b"stake 9.9", // proved for a different amount + ); + let err = env + .send(ix, &relay) + .expect_err("a tampered action payload was accepted"); + println!("payload tamper rejected: {err}"); + assert!(err.contains("Custom(18)"), "expected proof failure: {err}"); +} + +#[test] +fn an_action_cannot_re_enter_the_pool() { + // A payload that invokes mirror-pool itself would re-enter settlement in the + // middle of a lamport-moving loop. Refused explicitly rather than left to + // careful reading of the loop. + let (mut env, tree, notes, keys) = seeded_pool(6); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + let self_target = env.program_id; + let payload = b"reenter".as_slice(); + let proof = action_proof(&keys, &tree, ¬es, 2, &self_target, &beneficiary, payload); + let nullifier = proof.public_inputs[1]; + let ix = action_ix( + &env, + &proof, + nullifier, + &self_target, + &beneficiary, + &relay.pubkey(), + payload, + ); + env.send(ix, &relay).expect("submitting is allowed"); + + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + + // Alone, so wait out the timeout. + let mut clock = env.svm.get_sysvar::(); + clock.unix_timestamp += mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS + 1; + env.svm.set_sysvar(&clock); + + let batch = vec![(spend_pda, beneficiary, relay.insecure_clone())]; + let ix = settle_ix_with_targets(&env, &batch, &settler.pubkey(), &[Some(self_target)]); + let err = env.send(ix, &settler).expect_err("the pool invoked itself"); + println!("self-invocation rejected: {err}"); + assert!( + err.contains("Custom(23)"), + "expected SelfInvocationRefused: {err}" + ); +} diff --git a/programs/mirror-pool/tests/fixtures/spl_memo.so b/programs/mirror-pool/tests/fixtures/spl_memo.so new file mode 100644 index 0000000000000000000000000000000000000000..88385a01dec7b2f375cc33959fc8ee06e6eef409 GIT binary patch literal 74800 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 g^LN5a(; Date: Sat, 25 Jul 2026 07:09:34 -0300 Subject: [PATCH 22/83] feat: devnet deployment and live-cluster evidence Deployed to devnet at 8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa. The whole lifecycle ran against a real validator: pool creation, four deposits, four spends each carrying a Groth16 proof produced by the host prover and verified by the deployed program's own syscall, and a settlement paying out exactly four denominations. The vault ended at its rent-exempt minimum, which is the accounting invariant closing to the lamport. A replayed nullifier was rejected with this program's own error code rather than a bare runtime failure, because an error that could have come from anywhere is not evidence about this program. Two defects the live run exposed: The replay guard sat after proof verification, so a replay cost 94,999 compute units to reject. A spent nullifier is knowable from one account read; the guard now runs before the pairing and a replay costs 3,771. That matters because replays are what an attacker submits in bulk. The soak reconstructed the host tree from a formula over leaf indices, which only holds if every deposit ever made used that same formula. The program stores only the accumulator frontier, so past leaves are not recoverable from the chain at all. Notes are now persisted to a ledger written after each deposit, and the run refuses to start when the ledger and the pool disagree rather than building a root the program has never held. solana-client is deliberately not a dependency: it pulls transaction-status types from the far side of the ecosystem's wincode migration and cannot coexist with the version litesvm needs. The four RPC methods a live run needs are spoken directly instead, with backoff, since a 429 from a public endpoint is not a failure of the thing being measured. --- Cargo.lock | 30 ++ Cargo.toml | 2 +- crates/mirror-cli/Cargo.toml | 11 + crates/mirror-cli/src/chain.rs | 165 +++++++++ crates/mirror-cli/src/main.rs | 23 ++ crates/mirror-cli/src/soak.rs | 507 ++++++++++++++++++++++++++ data/program-id-devnet.txt | 1 + data/soak-notes.json | 1 + docs/PROOF.md | 34 ++ programs/mirror-pool/src/processor.rs | 37 +- 10 files changed, 793 insertions(+), 18 deletions(-) create mode 100644 crates/mirror-cli/src/chain.rs create mode 100644 crates/mirror-cli/src/soak.rs create mode 100644 data/program-id-devnet.txt create mode 100644 data/soak-notes.json create mode 100644 docs/PROOF.md diff --git a/Cargo.lock b/Cargo.lock index 1c3d4ee6..745b4be2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1829,14 +1829,25 @@ name = "mirror-cli" version = "0.1.0" dependencies = [ "anyhow", + "ark-std 0.5.0", + "base64", + "bincode", "clap", "hex", "mirror-circuit", "mirror-core", + "mirror-pool-program", "mirror-provenance", "serde", "serde_json", "sha2 0.10.9", + "solana-keypair", + "solana-message", + "solana-program", + "solana-signer", + "solana-system-interface", + "solana-transaction", + "ureq", ] [[package]] @@ -2360,6 +2371,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde_bytes" version = "0.11.19" @@ -3039,6 +3059,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8e7db78c122d02189619490b1fef04a2a042c389662920617c0e6381fdf8fdd" dependencies = [ "num-traits", + "serde", + "serde_derive", "solana-program-error", ] @@ -3163,6 +3185,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2666e0ff38557ad1239d0fc42f4688fb9df0817fb12c041dbae90c2b2e3fa55" dependencies = [ "blake3", + "serde", + "serde_derive", "solana-address 2.6.1", "solana-hash", "solana-instruction", @@ -3558,6 +3582,8 @@ dependencies = [ "ed25519-dalek", "five8", "serde", + "serde-big-array", + "serde_derive", "solana-sanitize", "wincode", ] @@ -3849,6 +3875,8 @@ version = "4.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6aa9b0f8543b99e1cb7db16a7c1a392139d82db57a90d262fb0a394807337bec" dependencies = [ + "serde", + "serde_derive", "solana-address 2.6.1", "solana-hash", "solana-instruction", @@ -3886,6 +3914,8 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a37cf28c58b03878d38c38411f0414d3a99a9e3b7e5ebb0f83f3317b2796d18" dependencies = [ + "serde", + "serde_derive", "solana-instruction-error", "solana-sanitize", ] diff --git a/Cargo.toml b/Cargo.toml index 73de06e1..4f4a2e36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -62,7 +62,7 @@ ureq = { version = "2", features = ["json"] } litesvm = "0.15" solana-keypair = "3.1" solana-signer = "3" -solana-transaction = "4.1" +solana-transaction = { version = "4.1", features = ["serde", "wincode"] } solana-message = "4.2" mirror-core = { path = "crates/mirror-core" } diff --git a/crates/mirror-cli/Cargo.toml b/crates/mirror-cli/Cargo.toml index 866543ff..57f5e64f 100644 --- a/crates/mirror-cli/Cargo.toml +++ b/crates/mirror-cli/Cargo.toml @@ -12,6 +12,17 @@ path = "src/main.rs" mirror-core.workspace = true mirror-circuit.workspace = true mirror-provenance.workspace = true +mirror-pool-program = { path = "../../programs/mirror-pool", default-features = false, features = ["no-entrypoint"] } +solana-keypair.workspace = true +solana-signer.workspace = true +solana-transaction = { workspace = true, features = ["serde", "wincode"] } +solana-program.workspace = true +solana-message.workspace = true +solana-system-interface.workspace = true +ark-std.workspace = true +ureq.workspace = true +base64 = "0.22" +bincode = "1.3" anyhow.workspace = true clap.workspace = true sha2.workspace = true diff --git a/crates/mirror-cli/src/chain.rs b/crates/mirror-cli/src/chain.rs new file mode 100644 index 00000000..21a19106 --- /dev/null +++ b/crates/mirror-cli/src/chain.rs @@ -0,0 +1,165 @@ +//! The few RPC calls a live run needs, over the same blocking client the +//! provenance collector uses. +//! +//! `solana-client` is deliberately not a dependency. It pulls +//! `solana-transaction-status-client-types`, which sits on the far side of the +//! ecosystem's in-progress `wincode` migration and cannot coexist with the +//! version litesvm requires. Rather than fight that resolution a second time, +//! this speaks JSON-RPC directly — four methods, all of which we already needed +//! to understand. + +use anyhow::{anyhow, Result}; +use base64::Engine; +use solana_program::pubkey::Pubkey; +use solana_transaction::Transaction; + +pub struct Chain { + endpoint: String, + agent: ureq::Agent, +} + +impl Chain { + pub fn new(endpoint: impl Into) -> Self { + Chain { + endpoint: endpoint.into(), + agent: ureq::AgentBuilder::new() + .timeout(std::time::Duration::from_secs(60)) + .build(), + } + } + + /// One call, retrying through rate limits. + /// + /// The public endpoints throttle aggressively and a 429 is not a failure of + /// the thing being measured — treating it as one would abandon a run that is + /// otherwise fine. Backoff is exponential and bounded, and a persistent 429 + /// still surfaces as an error rather than as a silent default. + fn call(&self, method: &str, params: serde_json::Value) -> Result { + let body = serde_json::json!({ + "jsonrpc": "2.0", "id": 1, "method": method, "params": params, + }); + let mut wait = std::time::Duration::from_millis(500); + let mut last: Option = None; + let mut response: Option = None; + for _ in 0..7 { + match self.agent.post(&self.endpoint).send_json(body.clone()) { + Ok(r) => { + response = Some(r.into_json()?); + break; + } + Err(ureq::Error::Status(429, _)) => { + std::thread::sleep(wait); + wait *= 2; + last = Some("rate limited".into()); + } + Err(e) => return Err(anyhow!("{method}: {e}")), + } + } + let response = response.ok_or_else(|| { + anyhow!( + "{method}: gave up after retries ({})", + last.unwrap_or_default() + ) + })?; + if let Some(err) = response.get("error") { + return Err(anyhow!("{method}: {err}")); + } + response + .get("result") + .cloned() + .ok_or_else(|| anyhow!("{method}: no result")) + } + + pub fn latest_blockhash(&self) -> Result { + let v = self.call( + "getLatestBlockhash", + serde_json::json!([{ "commitment": "confirmed" }]), + )?; + let s = v + .pointer("/value/blockhash") + .and_then(|b| b.as_str()) + .ok_or_else(|| anyhow!("blockhash missing"))?; + s.parse().map_err(|e| anyhow!("bad blockhash: {e}")) + } + + /// Sends and waits for confirmation, returning the signature. + /// + /// A failed simulation is surfaced with its logs rather than a bare error, + /// because the program's own error code is the thing worth recording. + pub fn send(&self, tx: &Transaction) -> Result { + let wire = bincode::serialize(tx)?; + let encoded = base64::engine::general_purpose::STANDARD.encode(&wire); + let sig = self.call( + "sendTransaction", + serde_json::json!([ + encoded, + { "encoding": "base64", "preflightCommitment": "confirmed" } + ]), + )?; + let sig = sig + .as_str() + .ok_or_else(|| anyhow!("signature missing"))? + .to_string(); + self.confirm(&sig)?; + Ok(sig) + } + + fn confirm(&self, signature: &str) -> Result<()> { + for _ in 0..40 { + let v = self.call( + "getSignatureStatuses", + serde_json::json!([[signature], { "searchTransactionHistory": true }]), + )?; + if let Some(status) = v.pointer("/value/0") { + if !status.is_null() { + if let Some(err) = status.get("err") { + if !err.is_null() { + return Err(anyhow!("transaction failed: {err}")); + } + } + let confirmed = status + .get("confirmationStatus") + .and_then(|c| c.as_str()) + .unwrap_or(""); + if confirmed == "confirmed" || confirmed == "finalized" { + return Ok(()); + } + } + } + std::thread::sleep(std::time::Duration::from_millis(1_200)); + } + Err(anyhow!("timed out waiting for {signature}")) + } + + pub fn account_data(&self, key: &Pubkey) -> Result>> { + let v = self.call( + "getAccountInfo", + // Commitment must be stated. The default is finalized, so a read + // immediately after a confirmed write sees nothing and the caller + // concludes the account was never created. + serde_json::json!([ + key.to_string(), + { "encoding": "base64", "commitment": "confirmed" } + ]), + )?; + let value = v.get("value").unwrap_or(&serde_json::Value::Null); + if value.is_null() { + return Ok(None); + } + let b64 = value + .pointer("/data/0") + .and_then(|d| d.as_str()) + .ok_or_else(|| anyhow!("account data missing"))?; + Ok(Some(base64::engine::general_purpose::STANDARD.decode(b64)?)) + } + + pub fn balance(&self, key: &Pubkey) -> Result { + let v = self.call( + "getBalance", + serde_json::json!([key.to_string(), { "commitment": "confirmed" }]), + )?; + v.pointer("/value") + .and_then(|b| b.as_u64()) + .ok_or_else(|| anyhow!("balance missing")) + } +} diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 5f9d125a..6865fe1f 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -5,6 +5,9 @@ use clap::{Parser, Subcommand}; use sha2::{Digest, Sha256}; use std::path::PathBuf; +mod chain; +mod soak; + /// The seed the committed verifying key was generated from. /// /// Public on purpose. It makes the setup reproducible — anyone can re-derive the @@ -113,6 +116,20 @@ enum Command { #[arg(long, default_value = "sample.json")] sample: PathBuf, }, + /// Runs the whole lifecycle against a live cluster and prints every + /// signature, so the result is checkable rather than asserted. + Soak { + #[arg(long)] + program: String, + #[arg(long, default_value = "https://api.devnet.solana.com")] + url: String, + /// Payer and settler keypair. + #[arg(long, default_value = "~/.config/solana/id.json")] + keypair: String, + /// Where to write the evidence. + #[arg(long, default_value = "docs/PROOF.md")] + out: PathBuf, + }, /// Recomputes the verifying key from a seed and reports its digest. /// /// This is the check a third party runs. It binds the *whole* key — alpha, @@ -400,6 +417,12 @@ fn main() -> Result<()> { } Ok(()) } + Command::Soak { + program, + url, + keypair, + out, + } => soak::run(&program, &url, &keypair, &out), Command::VerifySetup { seed, expect } => { let keys = mirror_circuit::generate_reproducible(seed.as_bytes()) .map_err(|e| anyhow::anyhow!("{e}"))?; diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs new file mode 100644 index 00000000..87e4b9b7 --- /dev/null +++ b/crates/mirror-cli/src/soak.rs @@ -0,0 +1,507 @@ +//! A live-cluster soak: the whole lifecycle against a real validator. +//! +//! Every assertion is a transaction that either landed or did not, and every +//! signature is printed so a third party can look it up. Nothing here is +//! simulated — the proof is generated by the host prover and verified by the +//! deployed program's own Groth16 syscall. + +use crate::chain::Chain; +use anyhow::{anyhow, Context, Result}; +use mirror_circuit::{generate_reproducible, prove, Keys, Witness}; +use mirror_core::{Field, MerkleTree, Note}; +use mirror_pool_program::{ + instruction::Instruction as MirrorIx, + pda::{pool_address, spend_address, vault_address}, + processor::SELECTOR_TRANSFER, +}; +use solana_keypair::Keypair; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use solana_signer::Signer; +use solana_transaction::Transaction; + +const DENOMINATION: u64 = 20_000_001; // 0.02 SOL; a pool is per-denomination, so + // this also picks out a fresh pool for the + // evidence run. +const ENTRY_FEE: u64 = 0; +const K_FLOOR: u32 = 4; +const RELAY_FEE: u64 = 200_000; + +/// One recorded step of the run. +pub struct Step { + pub name: &'static str, + pub signature: String, + pub note: String, +} + +pub struct Soak { + client: Chain, + program_id: Pubkey, + payer: Keypair, + pool: Pubkey, + vault: Pubkey, + pub steps: Vec, + pub negatives: Vec<(String, String)>, +} + +impl Soak { + pub fn new(url: &str, program_id: Pubkey, payer: Keypair) -> Self { + let (pool, _) = pool_address(&program_id, DENOMINATION); + let (vault, _) = vault_address(&program_id, &pool); + Soak { + client: Chain::new(url), + program_id, + payer, + pool, + vault, + steps: Vec::new(), + negatives: Vec::new(), + } + } + + fn send(&self, ix: Instruction, signers: &[&Keypair]) -> Result { + let blockhash = self.client.latest_blockhash()?; + let message = solana_message::Message::new(&[ix], Some(&signers[0].pubkey())); + let tx = Transaction::new(signers, message, blockhash); + self.client.send(&tx) + } + + fn record(&mut self, name: &'static str, signature: String, note: String) { + println!(" {name:<28} {signature}"); + self.steps.push(Step { + name, + signature, + note, + }); + } + + fn record_negative(&mut self, name: &str, err: String) { + // Only the program's own error code matters here; the rest is noise. + // Take only the hex code. The RPC embeds the error inside a JSON blob, + // so splitting on whitespace drags the rest of the payload along and the + // evidence table fills with noise instead of a code a reader can check. + let code = err + .split("custom program error: ") + .nth(1) + .map(|s| { + s.chars() + .take_while(|c| c.is_ascii_hexdigit() || *c == 'x') + .collect::() + }) + .filter(|s| !s.is_empty()) + .unwrap_or_else(|| "(no custom code)".to_string()); + println!(" {name:<28} rejected with {code}"); + self.negatives.push((name.to_string(), code)); + } + + fn account_meta_pool(&self) -> Vec { + vec![ + AccountMeta::new(self.payer.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(self.vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ] + } + + /// Creates the pool, unless a previous run already did. + pub fn init_pool(&mut self) -> Result<()> { + if self.client.account_data(&self.pool)?.is_some() { + println!(" pool already exists, reusing it"); + return Ok(()); + } + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::InitPool { + denomination: DENOMINATION, + entry_fee: ENTRY_FEE, + k_floor: K_FLOOR, + } + .pack(), + self.account_meta_pool(), + ); + let payer = self.payer.insecure_clone(); + let sig = self.send(ix, &[&payer])?; + self.record( + "init_pool", + sig, + format!("denomination {DENOMINATION}, k_floor {K_FLOOR}"), + ); + Ok(()) + } + + /// Deposits `count` notes and returns the host tree plus every note in it. + /// + /// Notes are persisted, not derived from a formula. The program stores only + /// the accumulator frontier, so the host cannot reconstruct past leaves from + /// the chain — and a formula only works if every deposit ever made used the + /// same one. A rerun that guessed wrong would build a root the program has + /// never held and every proof would fail with an unrelated error. + pub fn deposit( + &mut self, + count: u64, + ledger: &std::path::Path, + ) -> Result<(MerkleTree, Vec)> { + let denom_tag = Field::from_u64(DENOMINATION); + let mut tree = MerkleTree::new().map_err(|e| anyhow!("{e}"))?; + let mut notes = Vec::new(); + + let mut secrets: Vec<(u64, u64)> = if ledger.exists() { + serde_json::from_str(&std::fs::read_to_string(ledger)?)? + } else { + Vec::new() + }; + let on_chain = self.leaf_count()? as usize; + if secrets.len() != on_chain { + return Err(anyhow!( + "the note ledger holds {} notes but the pool has {on_chain} leaves. \ + They must agree, or the host tree cannot match the program's root. \ + Use a fresh denomination, or restore the ledger.", + secrets.len() + )); + } + for (k, r) in &secrets { + let note = Note::new(Field::from_u64(*k), Field::from_u64(*r), denom_tag); + tree.insert(note.commitment().map_err(|e| anyhow!("{e}"))?) + .map_err(|e| anyhow!("{e}"))?; + notes.push(note); + } + + let base = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH)? + .as_secs(); + for j in 0..count { + let k = base.wrapping_mul(1_000_003).wrapping_add(j * 7 + 1); + let r = base.wrapping_mul(7_919).wrapping_add(j * 13 + 1); + let note = Note::new(Field::from_u64(k), Field::from_u64(r), denom_tag); + let commitment = note.commitment().map_err(|e| anyhow!("{e}"))?; + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::Deposit { + commitment: commitment.to_bytes(), + } + .pack(), + self.account_meta_pool(), + ); + let payer = self.payer.insecure_clone(); + let sig = self.send(ix, &[&payer])?; + self.record("deposit", sig, format!("note {}", notes.len() + 1)); + tree.insert(commitment).map_err(|e| anyhow!("{e}"))?; + notes.push(note); + secrets.push((k, r)); + // Written after every deposit, so an interrupted run leaves a ledger + // that still matches the chain. + std::fs::write(ledger, serde_json::to_string(&secrets)?)?; + } + Ok((tree, notes)) + } + + fn leaf_count(&self) -> Result { + let mut data = self + .client + .account_data(&self.pool) + .context("reading the pool account")? + .ok_or_else(|| anyhow!("the pool account does not exist"))?; + let pool = mirror_pool_program::Pool::load(&mut data).map_err(|e| anyhow!("{e:?}"))?; + Ok(pool.deposit_count()) + } + + /// Submits a spend for `index`, with a real proof, relay-signed. + #[allow(clippy::too_many_arguments)] + pub fn submit_spend( + &mut self, + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + beneficiary: &Pubkey, + relay: &Keypair, + label: &'static str, + ) -> Result<(Pubkey, [u8; 32])> { + use ark_std::rand::SeedableRng; + let merkle_proof = tree.proof(index as u64).map_err(|e| anyhow!("{e}"))?; + let binding = mirror_core::action_binding( + SELECTOR_TRANSFER, + &[0u8; 32], + &beneficiary.to_bytes(), + RELAY_FEE, + &[], + ); + let witness = Witness { + note: notes[index], + merkle_proof: &merkle_proof, + root: tree.root().map_err(|e| anyhow!("{e}"))?, + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([index as u8 + 1; 32]); + let proof = prove(keys, &witness, &mut rng).map_err(|e| anyhow!("{e}"))?; + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&self.program_id, &self.pool, &nullifier); + + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR_TRANSFER, + target_program: [0u8; 32], + beneficiary: beneficiary.to_bytes(), + relay_fee: RELAY_FEE, + action_accounts: 0, + payload: Vec::new(), + } + .pack(), + vec![ + AccountMeta::new(relay.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let sig = self.send(ix, &[relay])?; + self.record(label, sig, format!("note {index}, relay-signed")); + Ok((spend_pda, nullifier)) + } + + /// Submits a spend expected to fail, recording the program's error code. + #[allow(clippy::too_many_arguments)] + pub fn expect_rejection( + &mut self, + name: &str, + ix: Instruction, + signers: &[&Keypair], + ) -> Result<()> { + match self.send(ix, signers) { + Ok(sig) => Err(anyhow!("{name} was accepted: {sig}")), + Err(e) => { + self.record_negative(name, e.to_string()); + Ok(()) + } + } + } + + pub fn settle(&mut self, batch: &[(Pubkey, Pubkey, Pubkey)]) -> Result<()> { + let mut metas = vec![ + AccountMeta::new(self.payer.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(self.vault, false), + ]; + for (spend, beneficiary, relay) in batch { + metas.push(AccountMeta::new(*spend, false)); + metas.push(AccountMeta::new(*beneficiary, false)); + metas.push(AccountMeta::new(*relay, false)); + } + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::SettleEpoch { + count: batch.len() as u8, + } + .pack(), + metas, + ); + let payer = self.payer.insecure_clone(); + let sig = self.send(ix, &[&payer])?; + self.record("settle_epoch", sig, format!("{} spends", batch.len())); + Ok(()) + } + + pub fn pool(&self) -> Pubkey { + self.pool + } + pub fn vault(&self) -> Pubkey { + self.vault + } + pub fn balance(&self, key: &Pubkey) -> Result { + self.client.balance(key) + } + pub fn program_id(&self) -> Pubkey { + self.program_id + } + pub fn spend_ix_for( + &self, + nullifier: [u8; 32], + proof: &mirror_circuit::SolanaProof, + beneficiary: &Pubkey, + relay: &Pubkey, + relay_fee: u64, + ) -> Instruction { + let (spend_pda, _) = spend_address(&self.program_id, &self.pool, &nullifier); + Instruction::new_with_bytes( + self.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR_TRANSFER, + target_program: [0u8; 32], + beneficiary: beneficiary.to_bytes(), + relay_fee, + action_accounts: 0, + payload: Vec::new(), + } + .pack(), + vec![ + AccountMeta::new(*relay, true), + AccountMeta::new(self.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ) + } +} + +/// The setup keys, derived from the committed public seed. +pub fn keys() -> Result { + generate_reproducible(crate::DEV_SETUP_SEED.as_bytes()).map_err(|e| anyhow!("{e}")) +} + +fn read_keypair(path: &str) -> Result { + let expanded = if let Some(rest) = path.strip_prefix("~/") { + format!("{}/{rest}", std::env::var("HOME")?) + } else { + path.to_string() + }; + let bytes: Vec = serde_json::from_str(&std::fs::read_to_string(&expanded)?)?; + Keypair::try_from(&bytes[..]).map_err(|e| anyhow!("reading {expanded}: {e}")) +} + +/// The full lifecycle, on a live cluster, with every signature recorded. +pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Result<()> { + let program_id: Pubkey = program + .parse() + .map_err(|e| anyhow!("bad program id: {e}"))?; + let payer = read_keypair(keypair)?; + println!("program {program_id}"); + println!("payer {}", payer.pubkey()); + println!("cluster {url}\n"); + + let mut soak = Soak::new(url, program_id, payer.insecure_clone()); + println!("keys: deriving from the committed public seed (this takes a moment)"); + let keys = keys()?; + + println!("\npositive path:"); + soak.init_pool()?; + let ledger = std::path::Path::new("data/soak-notes.json"); + let (tree, notes) = soak.deposit(K_FLOOR as u64, ledger)?; + + // Spend the notes this run deposited, not the earliest ones. A rerun + // against a pool that already holds spent notes would otherwise replay a + // burnt nullifier and stop. + let first_fresh = notes.len() - K_FLOOR as usize; + + // One relay and one beneficiary per member: no member key appears anywhere. + let mut batch = Vec::new(); + for i in first_fresh..first_fresh + K_FLOOR as usize { + let beneficiary = Keypair::new().pubkey(); + let relay = payer.insecure_clone(); + let (spend, _) = soak.submit_spend( + &keys, + &tree, + ¬es, + i, + &beneficiary, + &relay, + "submit_spend", + )?; + batch.push((spend, beneficiary, relay.pubkey())); + } + + let vault_before = soak.balance(&soak.vault())?; + soak.settle(&batch)?; + let vault_after = soak.balance(&soak.vault())?; + println!( + " vault {vault_before} -> {vault_after} (paid out {})", + vault_before.saturating_sub(vault_after) + ); + + println!("\nnegative path:"); + // A replayed proof: the nullifier record already exists. + { + use ark_std::rand::SeedableRng; + let beneficiary = Keypair::new().pubkey(); + let merkle_proof = tree.proof(first_fresh as u64).map_err(|e| anyhow!("{e}"))?; + let binding = mirror_core::action_binding( + SELECTOR_TRANSFER, + &[0u8; 32], + &beneficiary.to_bytes(), + RELAY_FEE, + &[], + ); + let witness = Witness { + note: notes[first_fresh], + merkle_proof: &merkle_proof, + root: tree.root().map_err(|e| anyhow!("{e}"))?, + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([first_fresh as u8 + 1; 32]); + let proof = prove(&keys, &witness, &mut rng).map_err(|e| anyhow!("{e}"))?; + let ix = soak.spend_ix_for( + proof.public_inputs[1], + &proof, + &beneficiary, + &payer.pubkey(), + RELAY_FEE, + ); + soak.expect_rejection("replayed nullifier", ix, &[&payer])?; + } + + write_proof(&soak, url, out)?; + println!("\nwrote {}", out.display()); + Ok(()) +} + +fn write_proof(soak: &Soak, url: &str, out: &std::path::Path) -> Result<()> { + let cluster = if url.contains("devnet") { + "devnet" + } else if url.contains("mainnet") { + "mainnet-beta" + } else { + "custom" + }; + let mut md = String::new(); + md.push_str("# Live-cluster evidence\n\n"); + md.push_str( + "Every line here is a transaction that landed. Nothing is simulated: the \ + proof is produced by the host prover and verified by the deployed \ + program's own Groth16 syscall.\n\n", + ); + md.push_str(&format!("- cluster: `{cluster}`\n")); + md.push_str(&format!("- program: `{}`\n", soak.program_id())); + md.push_str(&format!("- pool: `{}`\n", soak.pool())); + md.push_str(&format!("- vault: `{}`\n\n", soak.vault())); + + md.push_str("## Flows\n\n| step | signature | note |\n|---|---|---|\n"); + for s in &soak.steps { + md.push_str(&format!( + "| {} | [`{}`](https://explorer.solana.com/tx/{}?cluster={cluster}) | {} |\n", + s.name, s.signature, s.signature, s.note + )); + } + + md.push_str("\n## Rejections\n\n"); + md.push_str( + "A negative case is only evidence if the program's own error code is what \ + rejected it. A bare runtime failure proves nothing about this program.\n\n", + ); + md.push_str("| case | error code |\n|---|---|\n"); + for (name, code) in &soak.negatives { + md.push_str(&format!("| {name} | `{code}` |\n")); + } + + md.push_str("\n## Scope\n\n"); + md.push_str( + "Devnet is devnet. This is a live-cluster functional proof, not a claim of \ + mainnet operation or of an anonymity crowd: the run is scripted by one \ + operator with a handful of notes. What it establishes is that the circuit, \ + the prover, the byte layout and the deployed program agree on a real \ + validator.\n", + ); + std::fs::write(out, md)?; + Ok(()) +} diff --git a/data/program-id-devnet.txt b/data/program-id-devnet.txt new file mode 100644 index 00000000..2c737be0 --- /dev/null +++ b/data/program-id-devnet.txt @@ -0,0 +1 @@ +8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa diff --git a/data/soak-notes.json b/data/soak-notes.json new file mode 100644 index 00000000..2887fb35 --- /dev/null +++ b/data/soak-notes.json @@ -0,0 +1 @@ +[[1784979416922187,14135209596979],[1784979416922194,14135209596992],[1784979416922201,14135209597005],[1784979416922208,14135209597018],[1784979487922400,14135210159228],[1784979487922407,14135210159241],[1784979487922414,14135210159254],[1784979487922421,14135210159267]] \ No newline at end of file diff --git a/docs/PROOF.md b/docs/PROOF.md new file mode 100644 index 00000000..08b65698 --- /dev/null +++ b/docs/PROOF.md @@ -0,0 +1,34 @@ +# Live-cluster evidence + +Every line here is a transaction that landed. Nothing is simulated: the proof is produced by the host prover and verified by the deployed program's own Groth16 syscall. + +- cluster: `devnet` +- program: `8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa` +- pool: `CoxF5A4YG6bgtZ55WTYpLkXXJWC8oFbpsxbmfhvMe14W` +- vault: `HCkWvxyREJiLByGHbKt47uXJxjTubDM3V2Fs1zjmPu2p` + +## Flows + +| step | signature | note | +|---|---|---| +| deposit | [`4GCouwQ6xCierPJSoWWcpi7gq91k15YnuAbsQXMFXgK9XcSThsZ6xTamBcdt5R43o2LWUNNUDqosNpQFr48jtE9f`](https://explorer.solana.com/tx/4GCouwQ6xCierPJSoWWcpi7gq91k15YnuAbsQXMFXgK9XcSThsZ6xTamBcdt5R43o2LWUNNUDqosNpQFr48jtE9f?cluster=devnet) | note 5 | +| deposit | [`NidbwQVPYXtQ65pSs7i5GujuALQVbiKvwMz4mPahtURz9PQuz1qQ7K6ioYGwkYEKZ662LBo92NNHiQYCiJzbrXX`](https://explorer.solana.com/tx/NidbwQVPYXtQ65pSs7i5GujuALQVbiKvwMz4mPahtURz9PQuz1qQ7K6ioYGwkYEKZ662LBo92NNHiQYCiJzbrXX?cluster=devnet) | note 6 | +| deposit | [`2tkuqJAivXhhdqzgwHk432Pmz5WF7qUZbax5Pps4ufxeppnuiGSFYgxKXfvzfKht6phUEZ5pMDTKqMrY8SPwHz3j`](https://explorer.solana.com/tx/2tkuqJAivXhhdqzgwHk432Pmz5WF7qUZbax5Pps4ufxeppnuiGSFYgxKXfvzfKht6phUEZ5pMDTKqMrY8SPwHz3j?cluster=devnet) | note 7 | +| deposit | [`4amq6HyxccQtKPXcvpy6jNSQ8EhwxEybFA1f3sy8mFCBXy2PSrYcA5nnrQigiAMguEhVNMEsai3566DWP4UzhUuA`](https://explorer.solana.com/tx/4amq6HyxccQtKPXcvpy6jNSQ8EhwxEybFA1f3sy8mFCBXy2PSrYcA5nnrQigiAMguEhVNMEsai3566DWP4UzhUuA?cluster=devnet) | note 8 | +| submit_spend | [`2PDhAtCvDRFCW3u3nUaXvWSNjZkS72cdbNGoH1hAbpXoeGRnv4UcUCvnFMkscHnnW3M2kcFe722WieZApHmHrx8H`](https://explorer.solana.com/tx/2PDhAtCvDRFCW3u3nUaXvWSNjZkS72cdbNGoH1hAbpXoeGRnv4UcUCvnFMkscHnnW3M2kcFe722WieZApHmHrx8H?cluster=devnet) | note 4, relay-signed | +| submit_spend | [`2MoYdaFjZE3GjW6QRdARzrQjprRyHALzC5uEyN7cY7PK4qyCsr2j5AJ7LVc17Ldydk4UNhMR6Eh7TZnKCiydyS4L`](https://explorer.solana.com/tx/2MoYdaFjZE3GjW6QRdARzrQjprRyHALzC5uEyN7cY7PK4qyCsr2j5AJ7LVc17Ldydk4UNhMR6Eh7TZnKCiydyS4L?cluster=devnet) | note 5, relay-signed | +| submit_spend | [`2d5D67BzdtuPzpXEAZFqK6MCQr92Zn7TH5ynCvuVP5mhacNX7cpZ7o3w4CfzfK1DbRrzHnqiR1xN4az1xtHRu6Zw`](https://explorer.solana.com/tx/2d5D67BzdtuPzpXEAZFqK6MCQr92Zn7TH5ynCvuVP5mhacNX7cpZ7o3w4CfzfK1DbRrzHnqiR1xN4az1xtHRu6Zw?cluster=devnet) | note 6, relay-signed | +| submit_spend | [`413vqoRynW8WzAFaHuhGx8hrxTz9GcwWf5c5EPjig8Lkx4DUJyXA2RdoF7HhaMRgubciy8fr7tT1zM4UHe8rRdGd`](https://explorer.solana.com/tx/413vqoRynW8WzAFaHuhGx8hrxTz9GcwWf5c5EPjig8Lkx4DUJyXA2RdoF7HhaMRgubciy8fr7tT1zM4UHe8rRdGd?cluster=devnet) | note 7, relay-signed | +| settle_epoch | [`5WrDG3EoU2kwVe1rEdkiQ8tNP1YbxAkKobquV8QSQoJLJn6qCQg1pbAixSmpzDvJxPcXXtFx3fUWnpdrprbkrAZj`](https://explorer.solana.com/tx/5WrDG3EoU2kwVe1rEdkiQ8tNP1YbxAkKobquV8QSQoJLJn6qCQg1pbAixSmpzDvJxPcXXtFx3fUWnpdrprbkrAZj?cluster=devnet) | 4 spends | + +## Rejections + +A negative case is only evidence if the program's own error code is what rejected it. A bare runtime failure proves nothing about this program. + +| case | error code | +|---|---| +| replayed nullifier | `0xf` | + +## Scope + +Devnet is devnet. This is a live-cluster functional proof, not a claim of mainnet operation or of an anonymity crowd: the run is scripted by one operator with a handful of notes. What it establishes is that the circuit, the prover, the byte layout and the deployed program agree on a real validator. diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 2bcad32f..4f465f68 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -291,6 +291,26 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest } } + // The replay guard, and it runs before the pairing rather than after. + // + // A spent nullifier is knowable from one account read; verifying a proof + // costs about 95k compute units. Checking the cheap thing first means a + // replay is rejected at roughly 3k CU instead of 95k, which matters because + // replays are exactly what an attacker submits in bulk. + // + // It is also an explicit check rather than a reliance on account creation + // failing: a failed CPI terminates the instruction with the *system + // program's* error, so a replay would report a bare "already in use" that + // proves nothing about this program. Devnet evidence records error codes, + // and a code that could have come from anywhere is not evidence. + let (expected_spend, spend_bump) = spend_address(program_id, pool_account.key, &req.nullifier); + if *spend_account.key != expected_spend { + return Err(MirrorProgramError::InvalidPda.into()); + } + if !spend_account.data_is_empty() || spend_account.lamports() > 0 { + return Err(MirrorProgramError::NullifierAlreadySpent.into()); + } + // Recomputed, never transmitted. If the relay altered the selector, the // beneficiary or its own fee, this binding differs from the one the prover // committed to and the pairing fails. There is no separate field that could @@ -323,23 +343,6 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest .verify() .map_err(|_| MirrorProgramError::ProofVerificationFailed)?; - let (expected_spend, spend_bump) = spend_address(program_id, pool_account.key, &req.nullifier); - if *spend_account.key != expected_spend { - return Err(MirrorProgramError::InvalidPda.into()); - } - - // The replay guard, checked explicitly. - // - // Creating the account would fail on its own if it already existed, but a - // failed CPI terminates the instruction with the *system program's* error, - // so the transaction would report a bare "already in use" rather than - // anything of ours. Checking first means a replay produces a named code that - // devnet evidence can record and a third party can verify, instead of an - // error that could have come from anywhere. - if !spend_account.data_is_empty() || spend_account.lamports() > 0 { - return Err(MirrorProgramError::NullifierAlreadySpent.into()); - } - let rent = Rent::get()?; invoke_signed( &system_instruction::create_account( From 202ba3529e894032441766940ba57109ad5decd8 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 07:54:25 -0300 Subject: [PATCH 23/83] fix(provenance): two defects the first real analysis exposed, and the threat model MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first run of the analysis against real mainnet data refused to publish, which is the fail-closed machinery doing its job: the RPC failure rate was 2.70%, above the 1% threshold, and a headline computed from that would have been measuring our own throttling rather than the pool. Both defects below are why. The provenance client had no retry. The chain client used for the devnet soak got 429 backoff and this one did not, so a single rate limit abandoned a whole chain and became an RpcFailure. Enough of those and the run correctly refuses to report anything. Retries are bounded and persistent throttling still surfaces as an error — the point is to distinguish a transient limit from a real one, not to hide either. The volume-hub rule could never fire on the addresses it exists for. When paging hit the cap, the collector returned before recording the account's age, and the rule requires thirty days. So every address busy enough to exhaust the paging budget — which on the real sample was thirty chains out of thirty-seven — fell through to unresolved instead of being classified as the busy address it plainly is. The age is now taken from the oldest signature reached, which is conservative in the right direction: we stopped short of the true oldest, so an account that already looks thirty days old from a partial view is at least that. Both are pinned by tests, including one asserting that a page-capped funder *without* an age stays a budget outcome rather than quietly becoming evidence. Also adds docs/THREAT_MODEL.md, which spends more words on what does not hold than on what does: funding provenance is open and cannot be closed by a better circuit, the setup is reproducible rather than secure, the program is upgradeable, submission timing is relay policy rather than a guarantee, and amounts are public. --- crates/mirror-provenance/src/rpc.rs | 42 +- crates/mirror-provenance/src/trace.rs | 71 + data/sample-privacycash.json | 1984 +++++++++++++++++++++++++ docs/THREAT_MODEL.md | 126 ++ 4 files changed, 2213 insertions(+), 10 deletions(-) create mode 100644 data/sample-privacycash.json create mode 100644 docs/THREAT_MODEL.md diff --git a/crates/mirror-provenance/src/rpc.rs b/crates/mirror-provenance/src/rpc.rs index ed5948ee..a4711a39 100644 --- a/crates/mirror-provenance/src/rpc.rs +++ b/crates/mirror-provenance/src/rpc.rs @@ -124,26 +124,48 @@ impl RpcClient { self.last_call = Some(Instant::now()); } + /// One call, retrying through rate limits. + /// + /// A 429 is not evidence about the chain, so abandoning a trace on the first + /// one converts throttling into an `RpcFailure` — and enough of those push + /// the run past the threshold where it refuses to publish at all. Which is + /// what happened on the first real run of this collector: the chain client + /// had backoff and this one did not, and the analysis correctly refused a + /// headline computed from our own throttling. + /// + /// Persistent throttling still surfaces as an error. The point is not to + /// hide it, only to distinguish a transient limit from a real one. fn call( &mut self, method: &str, params: serde_json::Value, ) -> Result { - self.pace(); - self.calls += 1; let body = serde_json::json!({ "jsonrpc": "2.0", - "id": self.calls, + "id": self.calls + 1, "method": method, "params": params, }); - let response: JsonRpcResponse = self - .agent - .post(&self.endpoint) - .send_json(body) - .map_err(|e| RpcError::Transport(e.to_string()))? - .into_json() - .map_err(|e| RpcError::Shape(e.to_string()))?; + + let mut wait = std::time::Duration::from_millis(800); + let mut response: Option = None; + for attempt in 0..6 { + self.pace(); + self.calls += 1; + match self.agent.post(&self.endpoint).send_json(body.clone()) { + Ok(r) => { + response = Some(r.into_json().map_err(|e| RpcError::Shape(e.to_string()))?); + break; + } + Err(ureq::Error::Status(429 | 502 | 503, _)) if attempt < 5 => { + std::thread::sleep(wait); + wait *= 2; + } + Err(e) => return Err(RpcError::Transport(e.to_string())), + } + } + let response = response + .ok_or_else(|| RpcError::Transport(format!("{method}: throttled past retries")))?; if let Some(err) = response.error { return Err(RpcError::Rpc(err.to_string())); diff --git a/crates/mirror-provenance/src/trace.rs b/crates/mirror-provenance/src/trace.rs index 4078e129..3965d5dc 100644 --- a/crates/mirror-provenance/src/trace.rs +++ b/crates/mirror-provenance/src/trace.rs @@ -370,6 +370,7 @@ impl<'a> Collector<'a> { let mut before: Option = None; let mut seen: u64 = 0; let mut oldest: Option = None; + let mut last_seen_time: Option = None; for page in 0..self.config.sig_page_cap { let batch = self.client.signatures_for_address( @@ -383,6 +384,7 @@ impl<'a> Collector<'a> { seen += batch.len() as u64; let last = batch.last().expect("non-empty").clone(); before = Some(last.signature.clone()); + last_seen_time = last.block_time.or(last_seen_time); oldest = Some(last); if (batch.len() as u32) < self.config.page_size { @@ -394,6 +396,16 @@ impl<'a> Collector<'a> { // The cap is a classification signal with a name, never a // silent unresolved: the count is a lower bound and says so. facts.signatures = SigCount::AtLeast(seen); + // Record the age too, from the oldest signature reached. This + // is the whole point of the volume-hub rule: an address busy + // enough to exhaust the paging budget is exactly the kind that + // should be classified rather than dropped. + // + // The value is conservative in the right direction. We stopped + // before the true oldest signature, so the real account is at + // least this old; an address that already looks thirty days old + // from a partial view is genuinely at least that. + facts.first_seen = last_seen_time; return Ok(()); } } @@ -690,6 +702,65 @@ mod tests { assert_eq!(a.0[0].1.label().unwrap(), "busy-unlabelled:Hub"); } + /// An address busy enough to exhaust the paging budget is exactly the kind + /// the volume-hub rule exists for, so it must arrive at pass two carrying an + /// age. An earlier version returned from paging before recording one, and + /// every page-capped funder fell through to unresolved — which on a real + /// sample was thirty chains out of thirty-seven. + #[test] + fn a_page_capped_funder_is_classified_rather_than_dropped() { + let mut hub = wallet("BusyFunder"); + hub.signatures = SigCount::AtLeast(20_000); + hub.first_seen = Some(NOW - 400 * 24 * 3_600); + + let sample = sample_with( + vec![Chain { + seed: "member".into(), + visited: vec!["member".into(), "BusyFunder".into()], + stop: ChainStop::PageCapHit, + }], + vec![wallet("member"), hub], + ); + let (results, census) = + classify_sample(&sample, &AnchorSet::default(), &Thresholds::default(), NOW); + assert_eq!( + results[0].1.label().unwrap(), + "busy-unlabelled:BusyFunder", + "a page-capped funder with an age must classify" + ); + assert_eq!(census.resolved, 1); + assert_eq!(census.page_cap_hit, 0); + } + + /// Without an age it cannot classify, and that is the correct outcome — but + /// it must land in the budget bucket rather than look like evidence. + #[test] + fn a_page_capped_funder_without_an_age_stays_a_budget_outcome() { + let mut hub = wallet("BusyFunder"); + hub.signatures = SigCount::AtLeast(20_000); + hub.first_seen = None; + + let sample = sample_with( + vec![Chain { + seed: "member".into(), + visited: vec!["member".into(), "BusyFunder".into()], + stop: ChainStop::PageCapHit, + }], + vec![wallet("member"), hub], + ); + let (results, census) = + classify_sample(&sample, &AnchorSet::default(), &Thresholds::default(), NOW); + assert!(matches!( + results[0].1, + Outcome::Unresolved { + reason: Unresolved::PageCapHit, + .. + } + )); + assert_eq!(census.page_cap_hit, 1); + assert!(!Unresolved::PageCapHit.is_evidence()); + } + #[test] fn the_default_edge_threshold_admits_account_creation() { // Rent exemption for an empty account is about 890,880 lamports, so a diff --git a/data/sample-privacycash.json b/data/sample-privacycash.json new file mode 100644 index 00000000..c1949bae --- /dev/null +++ b/data/sample-privacycash.json @@ -0,0 +1,1984 @@ +{ + "manifest": { + "endpoint": "https://api.mainnet-beta.solana.com", + "first_available_block": 0, + "archival_probe_ok": true, + "collected_at": 1784973206, + "config": { + "scope": "Sol", + "depth_max": 5, + "min_edge_lamports": 500000, + "sig_page_cap": 20, + "page_size": 1000 + }, + "thresholds_hub_signatures": 5000, + "thresholds_distributor_fanout": 20, + "thresholds_cluster_min_size": 3, + "rpc_calls": 590, + "excluded_non_wallet": [ + "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", + "AxG818LtyEa88dJDoHeYHW2WyNXrCLYUzr6ifL5qn4KB", + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" + ], + "ambiguous_attribution_rate": 0.28846153846153844 + }, + "chains": [ + { + "seed": "B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc", + "visited": [ + "B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc" + ], + "stop": "PageCapHit" + }, + { + "seed": "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7", + "visited": [ + "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7", + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + ], + "stop": "PageCapHit" + }, + { + "seed": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "visited": [ + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165" + ], + "stop": "PageCapHit" + }, + { + "seed": "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4", + "visited": [ + "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4", + "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8" + ], + "stop": "PageCapHit" + }, + { + "seed": "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", + "visited": [ + "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z", + "visited": [ + "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z", + "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "visited": [ + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u", + "visited": [ + "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u", + "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L", + "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM", + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "stop": "PageCapHit" + }, + { + "seed": "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9", + "visited": [ + "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9", + "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd", + "G2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3t" + ], + "stop": "PageCapHit" + }, + { + "seed": "HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq", + "visited": [ + "HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB", + "visited": [ + "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB", + "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2", + "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS", + "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX", + "visited": [ + "DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M", + "visited": [ + "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M", + "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "visited": [ + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "visited": [ + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", + "visited": [ + "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me", + "visited": [ + "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me", + "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb", + "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR", + "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB", + "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE", + "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb" + ], + "stop": "DepthExceeded" + }, + { + "seed": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "visited": [ + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz" + ], + "stop": "RpcFailure" + }, + { + "seed": "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", + "visited": [ + "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", + "visited": [ + "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E" + ], + "stop": "PageCapHit" + }, + { + "seed": "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", + "visited": [ + "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", + "visited": [ + "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw", + "visited": [ + "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw", + "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7", + "visited": [ + "BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7" + ], + "stop": "PageCapHit" + }, + { + "seed": "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo", + "visited": [ + "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo", + "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L", + "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6", + "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug", + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "stop": "PageCapHit" + }, + { + "seed": "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h", + "visited": [ + "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h", + "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s" + ], + "stop": "PageCapHit" + }, + { + "seed": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", + "visited": [ + "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM" + ], + "stop": "PageCapHit" + }, + { + "seed": "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3", + "visited": [ + "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3", + "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q" + ], + "stop": "PageCapHit" + }, + { + "seed": "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", + "visited": [ + "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6", + "visited": [ + "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6", + "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW", + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "stop": "PageCapHit" + }, + { + "seed": "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL", + "visited": [ + "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL", + "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1", + "visited": [ + "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1", + "5otWRyzcb7D7DEGZoRJrn2RQ8mbGDDpWCntjh45fFKH1" + ], + "stop": "LocalTerminal" + }, + { + "seed": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "visited": [ + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz", + "visited": [ + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + ], + "stop": "PageCapHit" + }, + { + "seed": "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", + "visited": [ + "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "visited": [ + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP", + "visited": [ + "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP", + "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362", + "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw", + "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG", + "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU", + "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5" + ], + "stop": "DepthExceeded" + } + ], + "facts": { + "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix": { + "address": "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix", + "owner": null, + "executable": false, + "signatures": { + "Exact": 189 + }, + "first_seen": 1784608146, + "birth_edge": { + "sink": "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 153680469685, + "signature": "3ck9PBEzhy46jBLUWfaeoeEDLPFcC7wNpHzceyNWtzEte9HSiG3NafZb69iAZQHwAXpV5x6qFGWchcvH7QA4auJf", + "slot": 434235044, + "block_time": 1784608146, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS" + ] + }, + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy": { + "address": "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz": { + "address": "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM": { + "address": "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2857 + }, + "first_seen": 1769191683, + "birth_edge": { + "sink": "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM", + "sources": [ + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "value": 4502430000, + "signature": "5wKGv3SouFHh7HBkxVeJiR1H2gmEv4k8tm8xMAwGLMSEe8yGWUZn8jE36GDyZTQQ3PyPV1YhhSR7ELpa3JXYY3cA", + "slot": 395454220, + "block_time": 1769191683, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "funded": [ + "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L" + ] + }, + "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE": { + "address": "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 64 + }, + "first_seen": 1784607959, + "birth_edge": { + "sink": "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE", + "sources": [ + "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb" + ], + "value": 6000000000, + "signature": "2Bk6Vw6dmU8KDMvoyXeMbN5jmF7Rq2C2DkxMxjG4mkFvwfNFttNdAuPT5TkomyEipZ3Rbkh1tXHCACyKsgELM75N", + "slot": 434234594, + "block_time": 1784607959, + "ambiguous_attribution": false + }, + "fee_payers": [ + "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb" + ], + "funded": [ + "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB" + ] + }, + "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3": { + "address": "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1274 + }, + "first_seen": 1766003500, + "birth_edge": { + "sink": "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3", + "sources": [ + "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q" + ], + "value": 5000000000, + "signature": "2rKL8XiedJnXjJc1CkwpiQSxST2KARnr3BbLMn9zVmJb617A61cj9NJah2QxJz4GiV82ER4vinr6KJqGRNKboW1k", + "slot": 387377211, + "block_time": 1766003500, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q" + ], + "funded": [] + }, + "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL": { + "address": "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 8 + }, + "first_seen": 1784717266, + "birth_edge": { + "sink": "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL", + "sources": [ + "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2" + ], + "value": 150000000, + "signature": "GQw3x4cEkSMcwjTFMoxpJj25Rx21VinHRVhn5bcqACf1VxXLE4tMpCcScGZYspCfe2MVostgEU4fqix9SwcgVHr", + "slot": 434494932, + "block_time": 1784717266, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2" + ], + "funded": [] + }, + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr": { + "address": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9764 + }, + "first_seen": 1781265894, + "birth_edge": { + "sink": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 209692102518, + "signature": "2WnT866FtZdcXqRvfQoypXGhWSQRtqa4cCg453yjkGSuZA9fs8LJviugPbbGP9GYu3TDJhohtJeDwq1AuoeqJBFi", + "slot": 425977688, + "block_time": 1781265894, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s": { + "address": "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h" + ] + }, + "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9": { + "address": "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5445 + }, + "first_seen": 1718412672, + "birth_edge": { + "sink": "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9", + "sources": [ + "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd" + ], + "value": 86090000000, + "signature": "53RCfHfFHr7YhKq4wdNzoFBeMfwW1LFk7D2MRY2sXmkHKEdW1fqXFe5UHu9bkwnjxHFqmWC9rsQpF4quxzgrmirL", + "slot": 271894160, + "block_time": 1718412672, + "ambiguous_attribution": false + }, + "fee_payers": [ + "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd" + ], + "funded": [] + }, + "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR": { + "address": "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 8 + }, + "first_seen": 1784677471, + "birth_edge": { + "sink": "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR", + "sources": [ + "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB" + ], + "value": 6000000000, + "signature": "BgHQ66wJZyJ8Ku1X6nLcW3wTjWrz2tu3kXacjCxvFPmZqMyvZUPA6enxobD3mh4V7vF5AeyP3MCEzmP1Fp3aejo", + "slot": 434400144, + "block_time": 1784677471, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB" + ], + "funded": [ + "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb" + ] + }, + "5otWRyzcb7D7DEGZoRJrn2RQ8mbGDDpWCntjh45fFKH1": { + "address": "5otWRyzcb7D7DEGZoRJrn2RQ8mbGDDpWCntjh45fFKH1", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1" + ] + }, + "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G": { + "address": "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1675 + }, + "first_seen": 1781912647, + "birth_edge": { + "sink": "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 8341210350, + "signature": "66yx9Y9Kx2kDn414U79FzGUmHKqC6yjs2pwHYGVyKU7Hz78xoERgzk3aRLu6ER2Lsf8WSoYgKV5XmRBUoBjN1uCi", + "slot": 427608057, + "block_time": 1781912647, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo": { + "address": "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1455 + }, + "first_seen": 1777633794, + "birth_edge": { + "sink": "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo", + "sources": [ + "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L" + ], + "value": 258994628, + "signature": "5xTJvzkJ4KLtr61BQu17Zw5s48piQVRNqGfYW6uEF9Chz3pbYKKWj9SgEXgZqXZzwoWTWxfQwx1pk7pfmVk8xpea", + "slot": 416863953, + "block_time": 1777633794, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L" + ], + "funded": [] + }, + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF": { + "address": "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM" + ] + }, + "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs": { + "address": "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1798 + }, + "first_seen": 1782249772, + "birth_edge": { + "sink": "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 214667850, + "signature": "2uBFoGA742GiVkxExn7xvJwoswYHXze3H14E5jxJiPZVkSntt2nhG4awCL2jShRr7Dap88nokh7rahWEWf9N8BPx", + "slot": 428455127, + "block_time": 1782249772, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug": { + "address": "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 164 + }, + "first_seen": 1730137646, + "birth_edge": { + "sink": "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug", + "sources": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "value": 274487000, + "signature": "2DWsZNYcSuEfFqWocAACHHGpMFjNJjSFM6Af7tCd7qK7MvUVrT5zS3RTrMbQVS8DiNFyFZxn4d1KHbfJmXHH6Re3", + "slot": 298217842, + "block_time": 1730137646, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "funded": [ + "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6" + ] + }, + "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ": { + "address": "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2540 + }, + "first_seen": 1774048122, + "birth_edge": { + "sink": "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 2684550000, + "signature": "5JGkx4gLFdFnb8RyE1i2vHX8YhnXpf3jKopxGQJLt2W71QTbq9V8C5WWB1uUVgjVDemFL2Cr9hg7E7ovMD6br4SC", + "slot": 407765285, + "block_time": 1774048122, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7": { + "address": "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12414 + }, + "first_seen": 1773423037, + "birth_edge": { + "sink": "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7", + "sources": [ + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + ], + "value": 74999986000, + "signature": "5z7rRaVncRKAr8uKafQww17wURn5r1Nrso6rrjX33uWt4RcNDhZwJ3WnUzjQLrhPLvvarQUHiyF43yATFSwmngpR", + "slot": 406181631, + "block_time": 1773423037, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9WL2A89YBr6X47ABKYNzPentWiBA3H8tpaiuf5CaYHx6" + ], + "funded": [] + }, + "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4": { + "address": "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 75 + }, + "first_seen": 1777418100, + "birth_edge": { + "sink": "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4", + "sources": [ + "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8" + ], + "value": 1535976910, + "signature": "4QqKxxNWo8aMdnVwxt2HAZhr971T9NoiEoUrLLpao8iE6UYJP5P5v6Phpr6Dhmvr5CL8eC2gScqDPjNnRHdvxeig", + "slot": 416317031, + "block_time": 1777418100, + "ambiguous_attribution": false + }, + "fee_payers": [ + "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8" + ], + "funded": [] + }, + "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2": { + "address": "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 846 + }, + "first_seen": 1737286757, + "birth_edge": null, + "fee_payers": [ + "87XFc4oRJNVCihAhexTcDhr9CqZJcD5WafPecRJxY7Fh" + ], + "funded": [ + "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL" + ] + }, + "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me": { + "address": "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 166 + }, + "first_seen": 1784728932, + "birth_edge": { + "sink": "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me", + "sources": [ + "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb" + ], + "value": 6000000000, + "signature": "5egDP47NwEtjW6z3FGPu9C8TN6vuKzc5DWdW85GfBcPozErf2Hwz7SAT8uMRLqseRW1zJf9zyZDKzsRS7nU6NuE7", + "slot": 434522672, + "block_time": 1784728932, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb" + ], + "funded": [] + }, + "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L": { + "address": "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 749 + }, + "first_seen": 1769731281, + "birth_edge": { + "sink": "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L", + "sources": [ + "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM" + ], + "value": 1750000000, + "signature": "5QzbCSjB8yFUJJxW4VA6N3FMboK5PzFjLzmg17FA9D37Fwaob7mSBrihErDHAHqgBnHPVUfo5hLhaECpeXDKnRXv", + "slot": 396800592, + "block_time": 1769731281, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM" + ], + "funded": [ + "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u" + ] + }, + "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z": { + "address": "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784889281, + "birth_edge": { + "sink": "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z", + "sources": [ + "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi" + ], + "value": 357844785, + "signature": "3x7KfTZsQ3qacJ9YQ2GAkVNy5DH5fywQDuNDWHUPTSRdkZx2WXstn6r2aUuYNtexLskp9j3krmGMserB1uza47Xy", + "slot": 434904811, + "block_time": 1784889281, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi" + ], + "funded": [] + }, + "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E": { + "address": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1": { + "address": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2154 + }, + "first_seen": 1684141322, + "birth_edge": { + "sink": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "sources": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "value": 90000000, + "signature": "52ZPH7cfAzwauMYh529GgHWhTzRwReNdJyWYr5ZugsAB5gvSmP6CYuCV1U79B9SLxLjk6qy5nsDUmao8Xa5y5EBB", + "slot": 194024003, + "block_time": 1684141322, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "funded": [ + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6" + ] + }, + "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS": { + "address": "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS", + "owner": null, + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784763254, + "birth_edge": { + "sink": "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS", + "sources": [ + "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix" + ], + "value": 123619297270, + "signature": "2cECE1YoJRoHQy2qRWbyonojR7N6G7cAwCFK8RMz7suMaM1AczRPVJr9JNpjWZH6evSV42UcKpeetDTHEueNmXzG", + "slot": 434604242, + "block_time": 1784763254, + "ambiguous_attribution": false + }, + "fee_payers": [ + "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix" + ], + "funded": [ + "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2" + ] + }, + "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP": { + "address": "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4706 + }, + "first_seen": 1739322324, + "birth_edge": { + "sink": "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP", + "sources": [ + "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362" + ], + "value": 10000000000, + "signature": "7qWiwuckPXYWmebpR7dcDePFe2ck5fb9P4Vtcnia6q79VyEUvGGyioUbhEEfbb92v6afMhZZKQc9SLfjStygNLK", + "slot": 320058361, + "block_time": 1739322324, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362" + ], + "funded": [] + }, + "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2": { + "address": "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2", + "owner": null, + "executable": false, + "signatures": { + "Exact": 32 + }, + "first_seen": 1784763527, + "birth_edge": { + "sink": "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2", + "sources": [ + "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS" + ], + "value": 100000000000, + "signature": "wdtUyL83Tjg7qH1qdd8Tb13cf56tkME445ATpk5MsfdvVqnNrEPgdiShLcTgoErfnxSnBxRXkc2HNX1BC4vPbDb", + "slot": 434604898, + "block_time": 1784763527, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS" + ], + "funded": [ + "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB" + ] + }, + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2": { + "address": "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug" + ] + }, + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW": { + "address": "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", + "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi", + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix", + "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh", + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", + "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", + "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", + "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", + "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG", + "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", + "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + ] + }, + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ": { + "address": "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + ] + }, + "AxG818LtyEa88dJDoHeYHW2WyNXrCLYUzr6ifL5qn4KB": { + "address": "AxG818LtyEa88dJDoHeYHW2WyNXrCLYUzr6ifL5qn4KB", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6": { + "address": "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 461 + }, + "first_seen": 1730991438, + "birth_edge": { + "sink": "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6", + "sources": [ + "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug" + ], + "value": 170300000, + "signature": "3yotNo2nUtSpv7ygmKCi3Y6GA2Th4y1M4MZUfNSMFLJLLZgjpaZV8PTgncmDHp6LaTHbPudF6o8MtHPdZRa1YTkq", + "slot": 300020544, + "block_time": 1730991438, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug" + ], + "funded": [ + "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L" + ] + }, + "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb": { + "address": "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 118 + }, + "first_seen": 1784559066, + "birth_edge": { + "sink": "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb", + "sources": [ + "EH4vhUSeJrk17J5PkG4W2xziF1H8AVwLTH1AXx4ACvqt" + ], + "value": 6000000000, + "signature": "2DpouWNQzV4La7kGGFWEu8xAUrYsxX1iYLv2YjzUV7o52LGRTmoc2yBKdSeTYC2ftWLUaRyxa7dJoVi4d2aRX7md", + "slot": 434118998, + "block_time": 1784559066, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EH4vhUSeJrk17J5PkG4W2xziF1H8AVwLTH1AXx4ACvqt" + ], + "funded": [ + "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE" + ] + }, + "B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc": { + "address": "B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM": { + "address": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4": { + "address": "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6": { + "address": "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW" + ] + }, + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw": { + "address": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 155 + }, + "first_seen": 1780608496, + "birth_edge": { + "sink": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 3965000, + "signature": "2jPiYFrpAtgu9YXus8V7NPkAWW6xWy5zBdh42P7S5sticdDDVgPGo91ne8yDeLjXhZHyH6zxZnWg69yPXRApwheC", + "slot": 424324895, + "block_time": 1780608496, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7": { + "address": "BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr": { + "address": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784599658, + "birth_edge": { + "sink": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "sources": [ + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + ], + "value": 440000000, + "signature": "4QJkfNCAWjNh2mfYrx9WN6EBe6cCpPUABueLgf4WfqJ5smBUdqbHDcsQXAHT6d8a6q4hFNMvf7jAozFE4jw1rXwj", + "slot": 434214781, + "block_time": 1784599658, + "ambiguous_attribution": false + }, + "fee_payers": [ + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + ], + "funded": [] + }, + "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX": { + "address": "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1779951445, + "birth_edge": { + "sink": "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 8215125000, + "signature": "3S3LvvA4afcP2UWFcxxtrhRLYeEVx2nk5k516Eh5KFDaL6hv54unSFZsQKkJfwFyopqJoi3FBbspfAsLuEyzEGA4", + "slot": 422670074, + "block_time": 1779951445, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1": { + "address": "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784708703, + "birth_edge": { + "sink": "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1", + "sources": [ + "5otWRyzcb7D7DEGZoRJrn2RQ8mbGDDpWCntjh45fFKH1" + ], + "value": 8938355770, + "signature": "2AfWhhdPCHX43rGHUYcLfc9mKuadeZ9BcPwcZdjrdbU1GauBAvUpxeMxCtSwGkZgPftHpeHh4BzdaRMQHBvKkCza", + "slot": 434474544, + "block_time": 1784708703, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS": { + "address": "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784905824, + "birth_edge": { + "sink": "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1688050000, + "signature": "3okszdffPjqzZwqTbMnoDeW22XyxMzjeg4fH7Sa2zx87HW3sSGwHKbMjzoBbhMiodbFuw4pJREV7yyTMdHRyA6LX", + "slot": 434944185, + "block_time": 1784905824, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX": { + "address": "DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1784827497, + "birth_edge": null, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb": { + "address": "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 50 + }, + "first_seen": 1784677540, + "birth_edge": { + "sink": "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb", + "sources": [ + "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR" + ], + "value": 5972245550, + "signature": "3y1YYF3p7DGGbkcPfP7YZiCUuNVbWu1gkkQUyhbjGrHUWWWyHSK3g84KBRYKxWR5EwLHykacupHUUoPCuvixm9D5", + "slot": 434400306, + "block_time": 1784677540, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR" + ], + "funded": [ + "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me" + ] + }, + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW": { + "address": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 498 + }, + "first_seen": 1778972678, + "birth_edge": { + "sink": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 6632702930, + "signature": "vJcCHLLH6FaWPDarJWTvMdpKckbK9yPGhy9qgUgypRyheTmVdPiEMf5vCioqKT82iF8G7VZ95vXGuYWoECTAh2B", + "slot": 420215149, + "block_time": 1778972678, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr" + ] + }, + "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw": { + "address": "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw", + "owner": null, + "executable": false, + "signatures": { + "Exact": 263 + }, + "first_seen": 1739221109, + "birth_edge": { + "sink": "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw", + "sources": [ + "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG" + ], + "value": 193060356732, + "signature": "fAGMDMYWqckj3yppKkDz6nU16mw6nUyj8eqKz67cuBCk2vyPb41nDZFKVfUokuNdBBHR7DnRD6QVNTC1smJ6xeJ", + "slot": 319802609, + "block_time": 1739221109, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG" + ], + "funded": [ + "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362" + ] + }, + "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6": { + "address": "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784723655, + "birth_edge": { + "sink": "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6", + "sources": [ + "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW" + ], + "value": 53379956, + "signature": "5V2TrixS2SaFcCSTh7Z8rQCHwGk8BsHYxbVkiC1LYG7b1kroixqiZTc4xxgExXW799cbRzcxGZzHnNtJJqkfp7PY", + "slot": 434510180, + "block_time": 1784723655, + "ambiguous_attribution": false + }, + "fee_payers": [ + "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW" + ], + "funded": [] + }, + "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi": { + "address": "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi", + "owner": null, + "executable": false, + "signatures": { + "Exact": 22 + }, + "first_seen": 1784545211, + "birth_edge": { + "sink": "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 911592746, + "signature": "3mU1pusTugbjUavbBoZ22NW4vuCtQpkg3cLhD7LzftumBwNyYjjym77QgZDqw3P1MJyPGHmjFvhwfmBdqzkPM22C", + "slot": 434086683, + "block_time": 1784545211, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z" + ] + }, + "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L": { + "address": "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L", + "owner": null, + "executable": false, + "signatures": { + "Exact": 204 + }, + "first_seen": 1760973563, + "birth_edge": { + "sink": "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L", + "sources": [ + "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6" + ], + "value": 232124163, + "signature": "4myvBjchjS5GpkAefDQzdZy9X3tRiTAqLqXLP15CbfeRozQKLwNkE9cjT1HM1jWLgXWYX1Finn6WPATKvUW7qh1b", + "slot": 374643294, + "block_time": 1760973563, + "ambiguous_attribution": false + }, + "fee_payers": [ + "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6" + ], + "funded": [ + "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo" + ] + }, + "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q": { + "address": "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3" + ] + }, + "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG": { + "address": "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG", + "owner": null, + "executable": false, + "signatures": { + "Exact": 65 + }, + "first_seen": 1739220909, + "birth_edge": { + "sink": "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG", + "sources": [ + "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU" + ], + "value": 193060501712, + "signature": "3XTrWcuTAZ6VPTskSZUcKifL2no86sm4ySA1mKqd1TrtwfmBSH5TGFizJGSUj97L73oWhVHHZxDaZ8QPotXrhKHN", + "slot": 319802102, + "block_time": 1739220909, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU" + ], + "funded": [ + "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw" + ] + }, + "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M": { + "address": "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 39 + }, + "first_seen": 1784388028, + "birth_edge": { + "sink": "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M", + "sources": [ + "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh" + ], + "value": 713996614, + "signature": "3uB9YzeAF4ZxH7RhAuKzZTEdfY2ZemqdbocxsdNcgxhPTegQLCabEQV4FatVhr3suF7G3WsgTmZi1rM4Rv7bHwDY", + "slot": 433713669, + "block_time": 1784388028, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh" + ], + "funded": [] + }, + "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5": { + "address": "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU" + ] + }, + "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz": { + "address": "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1784474206, + "birth_edge": { + "sink": "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 4000000000, + "signature": "2HhrwGqohRREkYJwQikQSNMrdj84hNCE8rEm36vnehrxBDGhdcviJ8r2UaYrjVrkCdoc9C7yZgf1Ry5qqJkySMWe", + "slot": 433919635, + "block_time": 1784474206, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw": { + "address": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 14348 + }, + "first_seen": 1777091838, + "birth_edge": { + "sink": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 18571768434, + "signature": "44VV33bxUiT3rDmG3dtN2uxnDx4uBs6keq7PWCmx3rdqWhvPmPXJhrA7Sq7atABrqAkpaBZH5wdRRWMkFQ7j8kNA", + "slot": 415493545, + "block_time": 1777091838, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "G2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3t": { + "address": "G2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3t", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd" + ] + }, + "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB": { + "address": "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 70 + }, + "first_seen": 1784642108, + "birth_edge": { + "sink": "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB", + "sources": [ + "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE" + ], + "value": 6000000000, + "signature": "36emCx68q3daUpGUon62Ap25HKquJc4ShAsXFf4WTrQSv3LLkMyzrgHppntKE5x7nhXgc4yHAbiV9emJWZTyWUBt", + "slot": 434315972, + "block_time": 1784642108, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE" + ], + "funded": [ + "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR" + ] + }, + "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG": { + "address": "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784388623, + "birth_edge": { + "sink": "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1417613532, + "signature": "36LP8gnyhWKzZ7tRs8AqYiryPu1uXRnxkJkCL5UYY3gDLP9HDKxoM7F78gtGZhL4484ggeipkx7br8xuVkjqiCmP", + "slot": 433715069, + "block_time": 1784388623, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw" + ] + }, + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165": { + "address": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16": { + "address": "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7" + ] + }, + "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU": { + "address": "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU", + "owner": null, + "executable": false, + "signatures": { + "Exact": 88 + }, + "first_seen": 1739158938, + "birth_edge": { + "sink": "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU", + "sources": [ + "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5" + ], + "value": 175000000000, + "signature": "4oThEUiwo2rQL8uC6ftsU8CM4T777952pow6Xd1V1g38wLEXcD7Unwfoam7V3h3RscKvknzQYeniAxhqP788yJey", + "slot": 319645868, + "block_time": 1739158938, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5" + ], + "funded": [ + "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG" + ] + }, + "HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq": { + "address": "HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 14 + }, + "first_seen": 1784431964, + "birth_edge": null, + "fee_payers": [ + "5b89H4qf4gyBvEmdTbVCoEUxvBTMuqtjwpRnVusBwPF7" + ], + "funded": [] + }, + "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u": { + "address": "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 539 + }, + "first_seen": 1780432182, + "birth_edge": { + "sink": "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u", + "sources": [ + "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L" + ], + "value": 89094594, + "signature": "3AAmWu8c1yZAnghr6GUa2VZVep5GxNyWya4YaXB6nZvYXh3Au5z2tZGp8K4dDVV9gHzHoCVPkww6bCeRQkWnq9Am", + "slot": 423882128, + "block_time": 1780432182, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L" + ], + "funded": [] + }, + "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362": { + "address": "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362", + "owner": null, + "executable": false, + "signatures": { + "Exact": 489 + }, + "first_seen": 1739221320, + "birth_edge": { + "sink": "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362", + "sources": [ + "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw" + ], + "value": 193060212752, + "signature": "4ueh33xU53mTLFqenjNiZHvF9gZzqG5w7vbFG2Q3j45hwf2hgJWPdByjYckeqYBLc4Z2daRQ2z6svu5ia86Dm9gH", + "slot": 319803144, + "block_time": 1739221320, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw" + ], + "funded": [ + "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP" + ] + }, + "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU": { + "address": "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 145 + }, + "first_seen": 1784670576, + "birth_edge": { + "sink": "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 19924000000, + "signature": "3LziCnYj4ysFF65ZBR3SNn84RCPxARZLuPtZmpzURcX4DxBWNxnJfBLHPy59wws7xzfa1is1EEmFnFBVaKLNBeTo", + "slot": 434383701, + "block_time": 1784670576, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh": { + "address": "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784388026, + "birth_edge": { + "sink": "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 714001614, + "signature": "3zpvvnKV22nNgWn1Ydb4Cmuv1F4YKDfUYamgYGRpiorEU8miy9A1jphqfMqayhc7VCBVGe8rWwa9eid9YshWZ48o", + "slot": 433713663, + "block_time": 1784388026, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M" + ] + }, + "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd": { + "address": "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd", + "owner": null, + "executable": false, + "signatures": { + "Exact": 85 + }, + "first_seen": 1718409947, + "birth_edge": { + "sink": "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd", + "sources": [ + "G2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3t" + ], + "value": 36624826000, + "signature": "2obyvsC8f8UtJ8559U2kbcFKJWPo2MfRAWKv7QAJdMvm1hW2W16SLvaJnUWAwEexZPBsZ7s5e2dp38A7mTSPR9Cn", + "slot": 271887897, + "block_time": 1718409947, + "ambiguous_attribution": false + }, + "fee_payers": [ + "E9vf42zJXFv8Ljop1cG68NAxLDat4ZEGEWDLfJVX38GF" + ], + "funded": [ + "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9" + ] + }, + "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw": { + "address": "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 21 + }, + "first_seen": 1784388626, + "birth_edge": { + "sink": "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw", + "sources": [ + "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG" + ], + "value": 1417608532, + "signature": "4EZV6Ja1sYLxFkLSNn8iEM4HYdBjTiYcQvtxBvJv85YMEqHSAyarQKycubjButPaP8hLhohkykcr3rUZ2xNRhGu1", + "slot": 433715076, + "block_time": 1784388626, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG" + ], + "funded": [] + }, + "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW": { + "address": "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW", + "owner": null, + "executable": false, + "signatures": { + "Exact": 268 + }, + "first_seen": 1774254581, + "birth_edge": { + "sink": "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW", + "sources": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "value": 993510000, + "signature": "44FY8HTRxTcgN7VSbQRWWMcQKwr1GcJkUrNeRrn37hM2hkkKM2oknZDFKFT51NEERKch9e8SKE6MhNaFGuAvKQaH", + "slot": 408290047, + "block_time": 1774254581, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "funded": [ + "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6" + ] + }, + "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8": { + "address": "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 20000 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4" + ] + }, + "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h": { + "address": "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 6573 + }, + "first_seen": 1780811622, + "birth_edge": { + "sink": "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h", + "sources": [ + "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s" + ], + "value": 250000000, + "signature": "4Aosp2JL7xc7wzaEFspne89EtdUQ2k5KGX2ifGWFTu148Y9FrvDh8wEuW1wDa7pwMDXLBNbCf6d1z2vXQMLm5ciG", + "slot": 424834280, + "block_time": 1780811622, + "ambiguous_attribution": false + }, + "fee_payers": [ + "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s" + ], + "funded": [] + }, + "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB": { + "address": "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 859 + }, + "first_seen": 1784793041, + "birth_edge": { + "sink": "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB", + "sources": [ + "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2" + ], + "value": 97997217398, + "signature": "NUEDXVJvG4JREhaSjMytzbdG5Bm9CucuFuyCj9evrfefAkE79RzFyr38HcuvGEkG9c2SZUTQJtNPTssxoZugU4q", + "slot": 434675365, + "block_time": 1784793041, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2" + ], + "funded": [] + }, + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6": { + "address": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 531 + }, + "first_seen": 1783364451, + "birth_edge": { + "sink": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "sources": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + ], + "value": 1100000000, + "signature": "4kLBV2LbExCtK85mRP4FnNGDaHVTBNNvvtyQjbFVqiqeLgvs3fcSUVbsxtmuZ6WzVUBYV9h9GJzyUuWpYf6nAxTD", + "slot": 431221166, + "block_time": 1783364451, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + ], + "funded": [] + } + } +} \ No newline at end of file diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md new file mode 100644 index 00000000..f7260085 --- /dev/null +++ b/docs/THREAT_MODEL.md @@ -0,0 +1,126 @@ +# Threat model + +## What this protects + +An observer sees that an action happened and cannot say which member asked for +it. That is the whole claim, and everything below is either how it is achieved or +where it stops. + +## The adversary + +Passive, global, retrospective, chain-only. They read every transaction ever +made, run any analysis they like over it, and never need to compromise a key or +a machine. They may also label addresses using off-chain knowledge — exchange +deposit addresses, published attributions, their own records. + +They cannot break BN254 discrete log, invert Poseidon or find keccak collisions. + +## What holds + +**Deposit and action are unlinkable through the proof.** A spend proves +membership in the accumulator without naming a leaf. Against an adversary who +sees only the chain, the posterior over which member acted is uniform on the +pool's notes. + +**No member key appears on chain.** Spends are relay-signed and settlements are +settler-signed. A member's wallet never touches the protocol after depositing. + +**A relay cannot alter what was authorised.** The action binding covers the +selector, the target program, the beneficiary, the relay fee and the payload, and +is recomputed on-chain rather than transmitted. + +**Actions carry one signer.** The pool PDA invokes on every member's behalf, so +the on-chain trace of a stake made through the pool is identical whoever asked +for it. + +**Payouts share a timestamp.** Settlement batches, so arrival time does not +separate members within a batch. + +**Escrow cannot be drained.** The denomination is a pool constant and nullifiers +are spend-once, so `vault ≥ denomination × outstanding` is a function of two +counters that nothing a prover supplies can influence. + +**Nothing can hold a member's funds.** A member spends as their own relay and +settles their own batch after the timeout. No key's absence freezes anything. + +## What does not hold + +### Funding provenance — the open channel + +An adversary can partition members by where their capital came from. Learning a +member's class leaves only that class to guess within, so the anonymity that +survives is the size of the class rather than `k`. + +**This is not closed and cannot be by a better circuit.** No deposit pool +controls where its users' money came from. What we do instead: + +- the *action* side is closed, because actions execute from the pool PDA; +- the *membership* side is measured from real chain data, and the method, the + failure census and the sampling frame are published beside the number. + +`k_floor` bounds program-visible membership only. That is all a program can check. + +### The trusted setup is not secure + +It is *reproducible*, which is a different and lesser property. The seed is +public, so the toxic waste is public, so **proofs are forgeable by anyone who +runs the setup**. `mirror verify-setup` lets a third party re-derive the deployed +key and confirm it matches this circuit — that is what reproducibility buys, and +it is worth having, but it is not security. + +Production needs a multi-party ceremony. The scaffolding for one is not in this +submission and we do not claim it is. + +### The program is upgradeable + +Whoever holds the upgrade authority can replace the code, including with code +that steals escrow. This is a named trust assumption, not a property. +`solana program set-upgrade-authority --final` removes it and correspondingly +removes the ability to fix anything. + +### Timing at submission + +Settlement batches payouts, but `submit_spend` is a transaction at a time of the +relay's choosing. A relay that submits immediately on request leaks the member's +timing. This is relay policy, not a protocol guarantee, and we do not claim +otherwise. + +### Amounts are public + +Fixed denominations mean the amount is a pool constant rather than a secret. A +member who needs an unusual amount is identifiable by the pool they chose. There +is no confidential-value layer here. + +### Small crowds + +`k_floor` is enforced against notes in the tree, and a pool whose deposits are +mostly Sybils of one actor has a large nominal `k` and a small real one. The +entry fee prices set inflation; it does not prevent it. The provenance +measurement is the honest reading of what the set is worth. + +### Not audited + +No external review. The end-to-end suite runs against the compiled program on a +real SVM and the negative cases carry this program's own error codes, which is +evidence of behaviour and not a substitute for an audit. + +## Deliberate non-goals + +**Hiding funds.** The brief asks for behavioural deniability, not a mixer, and a +value-mixing layer is not here. + +**Defeating an adversary with off-chain data.** Someone who knows a member +deposited — because they watched them do it, or because the member told them — +is not in scope. The proof hides which member acted, not that a given person is +a member. + +**Compute-optimal proving.** The circuit is three public inputs at about 91k CU +because that is what binding everything that matters costs. It has not been +tuned further. + +## Claim language + +Words this project does not use about itself: *anonymous*, *untraceable*, +*unlinkable* without a named adversary and a stated population. Every quantitative +claim names the data it came from, and `docs/MEASUREMENT_LOG.md` records the runs +that produced nothing alongside the ones that did. From 4627a3d1aea34b6153387cd7354452dcc07206d7 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 16:02:17 -0300 Subject: [PATCH 24/83] docs: state why there is no mainnet deployment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Leaving it unexplained would read as something unfinished. It is a decision: the trusted setup is reproducible rather than secure, so proofs are forgeable by anyone who runs it, and a live pool with that property would be inviting deposits it cannot protect. Advertising a mainnet address while publishing a threat model that says proofs are forgeable would be incoherent. Devnet demonstrates everything mainnet would — same runtime, same alt_bn128 syscall, same verifier, same bytes. The prerequisite for mainnet is a real multi-party ceremony, not more SOL. Also adds the unresolved bracket to the metrics, which the methodology requires before any point estimate is publishable: unresolved members merged into one class is the most favourable reading, split into singletons the least, and both are reported because neither is known. --- README.md | 20 ++++ crates/mirror-cli/src/main.rs | 59 +++++++++++ crates/mirror-provenance/src/lib.rs | 2 +- crates/mirror-provenance/src/metrics.rs | 98 ++++++++++++++++++ crates/mirror-provenance/src/trace.rs | 50 ++++++++- data/sample-privacycash.json | 118 +++++++++++++++++----- data/seeds-privacycash.txt | 128 +++++++++++++++++------- 7 files changed, 414 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 91f7c86c..36ecb165 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,26 @@ And **"worst case is 1" is not a finding.** Under any heavy-tailed provenance prior somebody is always alone. It describes provenance in general, not the pool being measured. +## Deployment + +Live on **devnet** at `8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa`. The whole +lifecycle ran there against a real validator — pool creation, deposits, spends +each carrying a Groth16 proof verified by the deployed program's own syscall, and +a settlement that closed the vault to its rent-exempt minimum to the lamport. +Every signature is in `docs/PROOF.md`. + +**Not on mainnet, and that is a decision rather than an omission.** The trusted +setup here is reproducible, not secure: the seed is public, so the toxic waste is +public, so proofs are forgeable by anyone who runs the setup. A live pool with +that property would be inviting deposits it cannot protect. Publishing a threat +model that says proofs are forgeable and simultaneously advertising a mainnet +address would be incoherent. + +Devnet demonstrates everything mainnet would: same runtime, same `alt_bn128` +syscall, same verifier, same bytes. What mainnet would add is a claim about +readiness that this setup does not support yet. The prerequisite is a real +multi-party ceremony, not more SOL. + ## What we do not claim - Not "unlinkable", not "untraceable", not "anonymous" without a named adversary diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 6865fe1f..07759fd6 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -106,6 +106,13 @@ enum Command { rps: f64, #[arg(long, default_value_t = 6)] depth: u32, + /// Signature pages to walk before declaring an address high-activity. + /// + /// Raising it buys a better age estimate for busy funders, which is what + /// lets the volume-hub rule classify them instead of leaving them in the + /// budget bucket. + #[arg(long, default_value_t = 20)] + page_cap: u32, }, /// Pass two: classifies a committed sample and reports the anonymity ladder. /// @@ -287,6 +294,7 @@ fn main() -> Result<()> { endpoint, rps, depth, + page_cap, } => { let text = std::fs::read_to_string(&seeds) .with_context(|| format!("reading {}", seeds.display()))?; @@ -309,6 +317,7 @@ fn main() -> Result<()> { let config = mirror_provenance::CollectionConfig { depth_max: depth, + sig_page_cap: page_cap, ..Default::default() }; let thresholds = mirror_provenance::Thresholds::default(); @@ -377,6 +386,11 @@ fn main() -> Result<()> { .iter() .filter_map(|(_, o)| o.label().map(|s| s.to_string())) .collect(); + + // Members that reached no class, excluding our own failures: those + // belong to neither reading of the bracket. + let unresolved = census.measurable() - census.resolved; + match mirror_provenance::Anonymity::from_labels(&labels) { None => { println!("no member resolved to a class; nothing to report"); @@ -413,6 +427,51 @@ fn main() -> Result<()> { for (t, share) in &a.class_size_ccdf { println!(" t={t:<4} {:.4}", share); } + + // The bracket. A point estimate alone would not say whether + // the number is driven by what was measured or by what was + // not. + let mut sizes: std::collections::BTreeMap<&str, u64> = + std::collections::BTreeMap::new(); + for l in &labels { + *sizes.entry(l.as_str()).or_insert(0) += 1; + } + let resolved_sizes: Vec = sizes.into_values().collect(); + if let Some(b) = mirror_provenance::Bracket::new(&resolved_sizes, unresolved) { + println!(); + println!( + "unresolved bracket ({} resolved, {} unresolved):", + b.resolved, b.unresolved + ); + println!( + " rho {:.4} .. {:.4}", + b.lower.loss_factor, b.upper.loss_factor + ); + println!( + " effective-k {:.4} .. {:.4}", + b.lower.eff_k_shannon, b.upper.eff_k_shannon + ); + if !b.is_informative() { + println!(); + println!( + " NOT INFORMATIVE: fewer than half the members reached a class, so \ + the two readings\n diverge and either one quoted alone would \ + describe the sampling budget rather than\n the pool. The point \ + estimate above is reported for completeness, not as a result." + ); + } + } + + if a.good_turing_coverage < 0.8 { + println!(); + println!( + " UNDER-SAMPLED: Good-Turing coverage {:.2} and Chao1 estimates {:.0} \ + classes against\n {} observed, so most of the class distribution \ + was never seen. Effective-k measured at\n small k understates the \ + steady-state loss and does not extrapolate upward.", + a.good_turing_coverage, a.chao1, a.classes + ); + } } } Ok(()) diff --git a/crates/mirror-provenance/src/lib.rs b/crates/mirror-provenance/src/lib.rs index 45dc2b6e..c3e86586 100644 --- a/crates/mirror-provenance/src/lib.rs +++ b/crates/mirror-provenance/src/lib.rs @@ -22,7 +22,7 @@ pub use classify::{AnchorSet, Classifier, SetStructure, Thresholds}; pub use edge::{FundingEdge, TransactionView}; pub use facts::{AddressFacts, SigCount}; pub use frame::depositor_of; -pub use metrics::Anonymity; +pub use metrics::{Anonymity, Bracket}; pub use outcome::{Census, Outcome, TerminalRule, Unresolved}; pub use rpc::{RpcClient, RpcError}; pub use trace::{classify_sample, Chain, CollectionConfig, Collector, Sample, Scope}; diff --git a/crates/mirror-provenance/src/metrics.rs b/crates/mirror-provenance/src/metrics.rs index df66e58e..e38b59e8 100644 --- a/crates/mirror-provenance/src/metrics.rs +++ b/crates/mirror-provenance/src/metrics.rs @@ -174,6 +174,66 @@ impl Anonymity { } } +/// The bracket that must accompany any point estimate. +/// +/// Members whose trace did not reach a class are **not** a class. Merging them +/// assumes they are all alike, which is charitable to the pool and raises the +/// figure; splitting them into singletons assumes they are all distinct, which +/// is charitable to the adversary and lowers it. Neither is known, so both are +/// reported and the truth is somewhere between. +/// +/// A point estimate without this is not publishable output, because the reader +/// cannot tell whether the number is driven by what was measured or by what was +/// not. +#[derive(Debug, Clone, PartialEq)] +pub struct Bracket { + /// Unresolved merged into one class: the most favourable reading. + pub upper: Anonymity, + /// Unresolved split into singletons: the least favourable reading. + pub lower: Anonymity, + /// Members that reached a class. + pub resolved: u64, + /// Members that did not, excluding infrastructure failures. + pub unresolved: u64, +} + +impl Bracket { + /// Builds the bracket from resolved class sizes and a count of unresolved + /// members. + /// + /// `unresolved` must exclude RPC failures: those are our own and belong to + /// neither reading. + pub fn new(resolved_sizes: &[u64], unresolved: u64) -> Option { + let resolved: u64 = resolved_sizes.iter().sum(); + + let mut merged: Vec = resolved_sizes.to_vec(); + if unresolved > 0 { + merged.push(unresolved); + } + + let mut split: Vec = resolved_sizes.to_vec(); + split.extend(std::iter::repeat_n(1, unresolved as usize)); + + Some(Bracket { + upper: Anonymity::from_class_sizes(&merged)?, + lower: Anonymity::from_class_sizes(&split)?, + resolved, + unresolved, + }) + } + + /// Whether the bracket is tight enough for the point estimate to carry the + /// argument on its own. + /// + /// When most members are unresolved the two readings diverge, and quoting + /// either as *the* result would be reporting the sampling budget rather than + /// the pool. + pub fn is_informative(&self) -> bool { + let total = self.resolved + self.unresolved; + total > 0 && self.resolved * 2 >= total + } +} + #[cfg(test)] mod tests { use super::*; @@ -334,6 +394,44 @@ mod tests { assert!(close(at_one, 4.0 / 104.0, 1e-12)); } + #[test] + fn the_bracket_spans_both_readings_of_the_unresolved() { + // Six resolved in two classes of three, six unresolved. + let b = Bracket::new(&[3, 3], 6).unwrap(); + + // Merged: three classes of three, six, three. + assert_eq!(b.upper.nominal_k, 12); + // Split: two classes of three plus six singletons. + assert_eq!(b.lower.nominal_k, 12); + assert_eq!(b.lower.classes, 8); + assert_eq!(b.upper.classes, 3); + + assert!( + b.upper.eff_k_shannon > b.lower.eff_k_shannon, + "merging the unresolved must be the more favourable reading" + ); + assert!(b.upper.loss_factor > b.lower.loss_factor); + } + + #[test] + fn a_mostly_unresolved_sample_is_not_informative() { + // Two resolved, ten unresolved: the two readings are far apart and + // neither describes the pool. + let thin = Bracket::new(&[1, 1], 10).unwrap(); + assert!(!thin.is_informative()); + + let solid = Bracket::new(&[4, 4, 4], 3).unwrap(); + assert!(solid.is_informative()); + } + + #[test] + fn with_nothing_unresolved_the_bracket_collapses_to_a_point() { + let b = Bracket::new(&[5, 3, 2], 0).unwrap(); + assert_eq!(b.upper, b.lower); + assert_eq!(b.unresolved, 0); + assert!(b.is_informative()); + } + #[test] fn an_empty_or_degenerate_partition_is_refused() { assert!(Anonymity::from_class_sizes(&[]).is_none()); diff --git a/crates/mirror-provenance/src/trace.rs b/crates/mirror-provenance/src/trace.rs index 3965d5dc..c08339f9 100644 --- a/crates/mirror-provenance/src/trace.rs +++ b/crates/mirror-provenance/src/trace.rs @@ -113,9 +113,33 @@ pub struct Chain { pub stop: ChainStop, } +/// Strips credentials from an endpoint before it is written down. +/// +/// The manifest is a committed artifact, and provider URLs carry API keys in the +/// path or the query string. Recording the host tells a reader which provider +/// served the run — which is what they need to judge it — without publishing a +/// key that would then have to be rotated. +pub fn redact_endpoint(url: &str) -> String { + let without_query = url.split(['?', '#']).next().unwrap_or(url); + // Keep scheme and host, drop the path: Alchemy and Helius put the key there. + match without_query.split_once("://") { + Some((scheme, rest)) => { + let host = rest.split('/').next().unwrap_or(rest); + format!("{scheme}://{host}") + } + None => without_query + .split('/') + .next() + .unwrap_or(without_query) + .to_string(), + } +} + /// What the manifest records so a reader can judge the run without rerunning it. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct Manifest { + /// Host only. Any credential in the path or query is stripped by + /// [`redact_endpoint`] before this is written. pub endpoint: String, pub first_available_block: u64, pub archival_probe_ok: bool, @@ -211,7 +235,7 @@ impl<'a> Collector<'a> { } Sample { manifest: Manifest { - endpoint: check.endpoint.clone(), + endpoint: redact_endpoint(&check.endpoint), first_available_block: check.first_available_block, archival_probe_ok: check.archival_probe_ok, collected_at: now, @@ -761,6 +785,30 @@ mod tests { assert!(!Unresolved::PageCapHit.is_evidence()); } + /// The manifest is committed, so a provider key must never reach it. + #[test] + fn a_credential_never_reaches_the_manifest() { + for (url, expected) in [ + ( + "https://solana-mainnet.g.alchemy.com/v2/alch_SECRETKEY123", + "https://solana-mainnet.g.alchemy.com", + ), + ( + "https://mainnet.helius-rpc.com/?api-key=deadbeef-cafe", + "https://mainnet.helius-rpc.com", + ), + ( + "https://api.mainnet-beta.solana.com", + "https://api.mainnet-beta.solana.com", + ), + ] { + let got = redact_endpoint(url); + assert_eq!(got, expected); + assert!(!got.contains("SECRETKEY"), "key survived redaction: {got}"); + assert!(!got.contains("deadbeef"), "key survived redaction: {got}"); + } + } + #[test] fn the_default_edge_threshold_admits_account_creation() { // Rent exemption for an empty account is about 890,880 lamports, so a diff --git a/data/sample-privacycash.json b/data/sample-privacycash.json index c1949bae..9bdb7f5f 100644 --- a/data/sample-privacycash.json +++ b/data/sample-privacycash.json @@ -3,7 +3,7 @@ "endpoint": "https://api.mainnet-beta.solana.com", "first_available_block": 0, "archival_probe_ok": true, - "collected_at": 1784973206, + "collected_at": 1784975925, "config": { "scope": "Sol", "depth_max": 5, @@ -14,13 +14,13 @@ "thresholds_hub_signatures": 5000, "thresholds_distributor_fanout": 20, "thresholds_cluster_min_size": 3, - "rpc_calls": 590, + "rpc_calls": 627, "excluded_non_wallet": [ "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", "AxG818LtyEa88dJDoHeYHW2WyNXrCLYUzr6ifL5qn4KB", "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" ], - "ambiguous_attribution_rate": 0.28846153846153844 + "ambiguous_attribution_rate": 0.2777777777777778 }, "chains": [ { @@ -170,9 +170,11 @@ { "seed": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", "visited": [ - "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz" + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" ], - "stop": "RpcFailure" + "stop": "NoIncomingEdge" }, { "seed": "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", @@ -381,7 +383,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1772954466, "birth_edge": null, "fee_payers": [], "funded": [] @@ -491,7 +493,7 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 9764 + "Exact": 9767 }, "first_seen": 1781265894, "birth_edge": { @@ -518,7 +520,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1780918462, "birth_edge": null, "fee_payers": [], "funded": [ @@ -645,7 +647,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1784873051, "birth_edge": null, "fee_payers": [], "funded": [ @@ -873,7 +875,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1675029970, "birth_edge": null, "fee_payers": [], "funded": [] @@ -987,7 +989,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1783468695, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1001,7 +1003,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1782630865, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1029,7 +1031,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1784656384, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1107,7 +1109,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1768579501, "birth_edge": null, "fee_payers": [], "funded": [] @@ -1119,7 +1121,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1783225581, "birth_edge": null, "fee_payers": [], "funded": [] @@ -1143,7 +1145,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1784446498, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1182,7 +1184,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1743115811, "birth_edge": null, "fee_payers": [], "funded": [] @@ -1236,6 +1238,30 @@ ], "funded": [] }, + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz": { + "address": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11230 + }, + "first_seen": 1781801872, + "birth_edge": { + "sink": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "sources": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ], + "value": 100000000, + "signature": "2B1U3i2nZKDXancwuyt9UxUa7AJcqgiVyuXzZhECVSZwiy5CKYE7xe5KcEgGymmMS2FcifLQRD7PNgDhhE3zGEkJ", + "slot": 427328708, + "block_time": 1781801872, + "ambiguous_attribution": false + }, + "fee_payers": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ], + "funded": [] + }, "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1": { "address": "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1", "owner": "11111111111111111111111111111111", @@ -1330,7 +1356,7 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 498 + "Exact": 509 }, "first_seen": 1778972678, "birth_edge": { @@ -1462,7 +1488,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1775141178, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1526,7 +1552,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1758757924, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1558,6 +1584,22 @@ ], "funded": [] }, + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN": { + "address": "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18203 + }, + "first_seen": 1712861701, + "birth_edge": null, + "fee_payers": [ + "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J" + ], + "funded": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ] + }, "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw": { "address": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", "owner": "11111111111111111111111111111111", @@ -1590,7 +1632,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1776054108, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1657,7 +1699,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1776692095, "birth_edge": null, "fee_payers": [], "funded": [] @@ -1669,7 +1711,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1781821516, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1771,7 +1813,7 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 145 + "Exact": 156 }, "first_seen": 1784670576, "birth_edge": { @@ -1901,7 +1943,7 @@ "signatures": { "AtLeast": 20000 }, - "first_seen": null, + "first_seen": 1784781685, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1932,6 +1974,32 @@ ], "funded": [] }, + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe": { + "address": "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1946 + }, + "first_seen": 1762263640, + "birth_edge": { + "sink": "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "sources": [ + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + ], + "value": 10000000, + "signature": "2abx2yUwotMytWNz2EAiW6RTrAsz9otzWMXKPyVN8FjtaEa3hp4MTJYi2C3R8XrnUND8MyuMJ5T2yKEb4prkrBVJ", + "slot": 377877154, + "block_time": 1762263640, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + ], + "funded": [ + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz" + ] + }, "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB": { "address": "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB", "owner": "11111111111111111111111111111111", diff --git a/data/seeds-privacycash.txt b/data/seeds-privacycash.txt index 37a246d2..98b64b55 100644 --- a/data/seeds-privacycash.txt +++ b/data/seeds-privacycash.txt @@ -1,40 +1,100 @@ -B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc -75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7 +FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B +5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3 +FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k +8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E +5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za +3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze +D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU +4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd +DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff +2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ +6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk +GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp +J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v +5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ +C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD +6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7 +GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq +2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf +74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB +E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do +BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P +7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF +DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U +2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165 -7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4 -DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS -8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw -HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u -5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9 -HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq -sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB -BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4 -DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX -AxG818LtyEa88dJDoHeYHW2WyNXrCLYUzr6ifL5qn4KB -FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M +Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC +9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu +5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr +8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT +Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9 +81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw -4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr -2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy -6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs -8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me +9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY +HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k +HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC +6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx +8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u +8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz -FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz -8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E -Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX -65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G -SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw -BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7 -6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo -isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h -BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM -3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3 -6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ -EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6 -3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL -CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1 +3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv +F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL +4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr +Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis +GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT +DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX +22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma +5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV +AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo +75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6 -2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz -HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU +8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U +5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p +BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ +7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG +LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE +BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo +5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t +iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW +6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq +BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38 +5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS +8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1 +B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ +CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU +C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA +CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh +GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk +E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq +7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW +1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E +4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16 +2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha +FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr -9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP +2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg +9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu +9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn +7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG +8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH +F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp +HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq +3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL +88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH +3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7 +Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX +BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG +EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH +HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ +F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx +DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR +2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9 +9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp +6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY +JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ +F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ +FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM +3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H +3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH +BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM From 1d1480484df76d1bc9ebe3d4d6d09dfbb76dc6dc Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 16:47:37 -0300 Subject: [PATCH 25/83] fix: two griefing vectors that destroyed deposits, and a verify that could not pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An adversarial review found no theft path — the accounting invariant holds, the circuit is sound, nullifiers are spend-once and duplicate accounts in one batch fail closed. What it found instead were ways for a third party to permanently destroy a member's deposit for free, which loses the same money by another route. action_accounts was relay-supplied, written into the spend record without validation, and outside the action binding. A relay handed a valid transfer proof could submit it with a count settlement can never satisfy: the pairing passes because the binding did not cover the field, the nullifier burns, and settlement then refuses forever. No instruction amends or refunds a spend, so the note is destroyed at zero cost to the relay beyond the fee it forfeits. That falsified a sentence in the threat model — "the binding covers every field with economic or behavioural meaning" — which was simply not true. The field is now in the preimage, and selector and count are validated at submit rather than at settlement, because reaching settlement already means the nullifier has burned. k_floor had no upper bound. Pools are unique per denomination and creation is permissionless, so a griefer paying one pool's rent could set a floor above the tree's capacity and make every spend fail permanently — taking every later depositor's funds with it and preventing an honest pool for that denomination from ever existing. Bounded now between 2 and the tree capacity, and the entry fee must stay below the denomination. A floor of one was also accepted, which is the anonymity set of one the check was supposed to prevent. Regressions for both, including one asserting the note is still spendable after a failed griefing attempt. `make verify` also could not pass on a fresh clone: it ran the tests before build-sbf, and the end-to-end suite loads the .so that build-sbf produces. The one command the README tells a reader to run failed at the first thing they would try. The ordering is fixed and checked by deleting the artifact and running it. CI ran the host checks and the SBF build as separate jobs on separate runners, so twenty end-to-end tests ran in a job with no Solana toolchain and could only panic, and the artifact never crossed between them. CI now runs `make verify` — the same command the README documents — so the two cannot drift apart again. --- .github/workflows/ci.yml | 40 ++++---- Makefile | 7 +- crates/mirror-cli/src/main.rs | 20 +++- crates/mirror-cli/src/soak.rs | 2 + crates/mirror-core/src/hash.rs | 34 +++++-- crates/mirror-provenance/src/trace.rs | 56 ++++++++++- programs/mirror-pool/src/processor.rs | 14 +++ programs/mirror-pool/src/state.rs | 82 +++++++++++++-- programs/mirror-pool/tests/end_to_end.rs | 122 ++++++++++++++++++++++- 9 files changed, 334 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88c5049b..c0365b32 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,12 +9,18 @@ env: CARGO_TERM_COLOR: always RUSTFLAGS: -D warnings +# One job running `make verify`, which is the command the README tells a reader +# to run. Splitting the host checks from the SBF build let them drift: the +# end-to-end suite needs the .so, so a job without the Solana toolchain ran +# twenty tests that could only panic, and the artifact never crossed between +# runners. If CI and the documented command are the same command, they cannot +# disagree. jobs: - check: - name: fmt / clippy / test + verify: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + - name: Cache cargo uses: actions/cache@v4 with: @@ -23,23 +29,21 @@ jobs: ~/.cargo/git target key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - run: cargo fmt --all -- --check - - run: cargo clippy --workspace --all-targets --all-features - - run: cargo test --workspace --all-features - program: - name: build-sbf - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Install Agave toolchain + - name: Cache the Solana toolchain + uses: actions/cache@v4 + with: + path: ~/.local/share/solana + key: ${{ runner.os }}-solana-stable + + - name: Install Agave run: | - sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" + if [ ! -x "$HOME/.local/share/solana/install/active_release/bin/solana" ]; then + sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" + fi echo "$HOME/.local/share/solana/install/active_release/bin" >> "$GITHUB_PATH" + - run: solana --version - - name: Build the on-chain program - # Isolated target dir: see the note in the Makefile. Sharing one with the - # host build makes cargo feed a host .dylib to the SBF rustc. - run: make build-sbf - - name: Run the on-chain test suite against the built .so - run: cargo test -p mirror-pool-program --all-features + + - name: make verify + run: make verify diff --git a/Makefile b/Makefile index 9d9cd099..c116b542 100644 --- a/Makefile +++ b/Makefile @@ -36,7 +36,12 @@ build-sbf: @ls -l $(SBF_TARGET_DIR)/deploy/*.so # Everything CI runs, in the order CI runs it. -verify: lint test build-sbf +# +# build-sbf comes before test, and the order is load-bearing: the end-to-end +# suite loads the .so this produces into a real SVM, and the artifact is +# gitignored. With test first, `make verify` fails on a fresh clone at the one +# command the README tells a reader to run. +verify: lint build-sbf test clean: cargo clean diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 07759fd6..529cca61 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -326,7 +326,25 @@ fn main() -> Result<()> { .as_secs() as i64; let collector = mirror_provenance::Collector::new(&mut client, config); - let sample = collector.collect(&seed_list, &check, &thresholds, now); + let started = std::time::Instant::now(); + let sample = + collector.collect_with_progress(&seed_list, &check, &thresholds, now, |p| { + // Rate is the useful number here: it tells the operator + // whether the run is progressing or the endpoint has + // started throttling. + let elapsed = started.elapsed().as_secs_f64().max(0.001); + eprintln!( + " [{:>3}/{}] {}… {} hops, {:?} ({} calls, {:.1}/s, {:.0}s elapsed)", + p.done, + p.total, + &p.seed[..p.seed.len().min(8)], + p.hops, + p.stop, + p.rpc_calls, + p.rpc_calls as f64 / elapsed, + elapsed, + ); + }); std::fs::write(&out, sample.to_json()?) .with_context(|| format!("writing {}", out.display()))?; diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs index 87e4b9b7..0cee1c73 100644 --- a/crates/mirror-cli/src/soak.rs +++ b/crates/mirror-cli/src/soak.rs @@ -226,6 +226,7 @@ impl Soak { &[0u8; 32], &beneficiary.to_bytes(), RELAY_FEE, + 0, &[], ); let witness = Witness { @@ -431,6 +432,7 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re &[0u8; 32], &beneficiary.to_bytes(), RELAY_FEE, + 0, &[], ); let witness = Witness { diff --git a/crates/mirror-core/src/hash.rs b/crates/mirror-core/src/hash.rs index a6bc1900..2b5e6b50 100644 --- a/crates/mirror-core/src/hash.rs +++ b/crates/mirror-core/src/hash.rs @@ -116,16 +116,27 @@ pub fn field_from_digest(digest: [u8; 32]) -> Field { /// | `beneficiary` | redirect the outcome | /// | `relay_fee` | inflate its own cut | /// | `payload` | change the action's parameters | +/// | `action_accounts` | declare a count settlement cannot satisfy | +/// +/// `action_accounts` is here because leaving it out was a live griefing vector: +/// it is relay-supplied, it is written verbatim into the spend record, and +/// settlement refuses any spend whose declared count does not match its +/// selector. A relay handed a valid transfer proof could submit it with +/// `action_accounts = 1`, burn the nullifier, and leave a note that can never +/// settle and can never be refunded — for free, and undetectably until +/// settlement. /// /// Keccak rather than Poseidon because the payload is variable length and /// Poseidon is a fixed-arity compression. The circuit never computes this — it /// takes the result as an opaque public input — so the choice costs no /// constraints. +#[allow(clippy::too_many_arguments)] pub fn action_binding( selector: u64, target_program: &[u8; 32], beneficiary: &[u8; 32], relay_fee: u64, + action_accounts: u8, payload: &[u8], ) -> Field { let digest = solana_keccak_hasher::hashv(&[ @@ -134,6 +145,7 @@ pub fn action_binding( target_program, beneficiary, &relay_fee.to_le_bytes(), + &[action_accounts], payload, ]); field_from_digest(digest.to_bytes()) @@ -201,7 +213,7 @@ mod tests { fn the_action_binding_covers_every_field_a_relay_could_change() { let bob = [7u8; 32]; let payload = b"stake 0.1".as_slice(); - let base = action_binding(1, &PROG, &bob, 5_000, payload); + let base = action_binding(1, &PROG, &bob, 5_000, 0, payload); let mut carol = [7u8; 32]; carol[31] = 8; @@ -210,35 +222,35 @@ mod tests { assert_ne!( base, - action_binding(2, &PROG, &bob, 5_000, payload), + action_binding(2, &PROG, &bob, 5_000, 0, payload), "selector" ); assert_ne!( base, - action_binding(1, &other_prog, &bob, 5_000, payload), + action_binding(1, &other_prog, &bob, 5_000, 0, payload), "target program" ); assert_ne!( base, - action_binding(1, &PROG, &carol, 5_000, payload), + action_binding(1, &PROG, &carol, 5_000, 0, payload), "beneficiary" ); assert_ne!( base, - action_binding(1, &PROG, &bob, 6_000, payload), + action_binding(1, &PROG, &bob, 6_000, 0, payload), "relay fee" ); assert_ne!( base, - action_binding(1, &PROG, &bob, 5_000, b"stake 1.0"), + action_binding(1, &PROG, &bob, 5_000, 0, b"stake 1.0"), "payload" ); } #[test] fn the_binding_is_deterministic_and_lands_in_the_field() { - let a = action_binding(1, &PROG, &[9u8; 32], 1, b"x"); - let b = action_binding(1, &PROG, &[9u8; 32], 1, b"x"); + let a = action_binding(1, &PROG, &[9u8; 32], 1, 0, b"x"); + let b = action_binding(1, &PROG, &[9u8; 32], 1, 0, b"x"); assert_eq!(a, b); // Canonical by construction: the top byte is cleared. assert_eq!(a.to_bytes()[0], 0); @@ -248,8 +260,8 @@ mod tests { #[test] fn an_empty_payload_is_a_distinct_action_from_a_zero_byte_one() { assert_ne!( - action_binding(1, &PROG, &[1u8; 32], 0, b""), - action_binding(1, &PROG, &[1u8; 32], 0, b"\0"), + action_binding(1, &PROG, &[1u8; 32], 0, 0, b""), + action_binding(1, &PROG, &[1u8; 32], 0, 0, b"\0"), ); } @@ -268,7 +280,7 @@ mod tests { fn the_domain_tag_separates_bindings_from_raw_hashes() { // Without the tag, a preimage assembled elsewhere could collide with a // binding. With it, an attacker must also control the tag. - let with_tag = action_binding(0, &[0u8; 32], &[0u8; 32], 0, b""); + let with_tag = action_binding(0, &[0u8; 32], &[0u8; 32], 0, 0, b""); let raw = field_from_digest( solana_keccak_hasher::hashv(&[ &0u64.to_le_bytes(), diff --git a/crates/mirror-provenance/src/trace.rs b/crates/mirror-provenance/src/trace.rs index c08339f9..8e5f0a02 100644 --- a/crates/mirror-provenance/src/trace.rs +++ b/crates/mirror-provenance/src/trace.rs @@ -105,6 +105,26 @@ impl ChainStop { } } +/// What a caller learns as each seed completes. +/// +/// Collection is bounded by network latency, not by work, so a run over a few +/// hundred seeds takes tens of minutes with nothing to show for it. Reporting +/// per seed is the difference between a tool that looks hung and one that does +/// not. +/// +/// A callback rather than printing: a library that writes to stdout takes a +/// decision that belongs to whoever is calling it. +#[derive(Debug, Clone, Copy)] +pub struct Progress<'a> { + pub done: usize, + pub total: usize, + pub seed: &'a str, + pub hops: usize, + pub stop: ChainStop, + /// RPC calls made across the whole run so far. + pub rpc_calls: u64, +} + /// One member's provenance chain, seed first. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Chain { @@ -215,22 +235,48 @@ impl<'a> Collector<'a> { /// Collects every seed and assembles the sample. pub fn collect( + self, + seeds: &[String], + check: &EndpointCheck, + thresholds: &Thresholds, + now: i64, + ) -> Sample { + self.collect_with_progress(seeds, check, thresholds, now, |_| {}) + } + + /// Collects every seed, reporting each one as it completes. + pub fn collect_with_progress)>( mut self, seeds: &[String], check: &EndpointCheck, thresholds: &Thresholds, now: i64, + mut on_progress: F, ) -> Sample { let mut chains = Vec::with_capacity(seeds.len()); let mut excluded_non_wallet = Vec::new(); - for seed in seeds { - match self.is_wallet(seed) { - Ok(true) => chains.push(self.trace(seed)), - Ok(false) => excluded_non_wallet.push(seed.clone()), + for (index, seed) in seeds.iter().enumerate() { + let chain = match self.is_wallet(seed) { + Ok(true) => Some(self.trace(seed)), + Ok(false) => { + excluded_non_wallet.push(seed.clone()); + None + } // An endpoint failure here is not evidence that the seed is not // a wallet, so it stays in the frame and fails honestly during // the trace. - Err(_) => chains.push(self.trace(seed)), + Err(_) => Some(self.trace(seed)), + }; + if let Some(chain) = chain { + on_progress(Progress { + done: index + 1, + total: seeds.len(), + seed, + hops: chain.visited.len(), + stop: chain.stop, + rpc_calls: self.client.calls_made(), + }); + chains.push(chain); } } Sample { diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 4f465f68..60d7093d 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -318,11 +318,25 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest if req.payload.len() > MAX_PAYLOAD { return Err(MirrorProgramError::PayloadTooLarge.into()); } + // Refuse a shape settlement could never satisfy, here rather than at + // settlement. Reaching settlement means the nullifier has already burned, + // and there is no instruction that can amend or refund a spend — so a + // record that cannot settle is a note destroyed. The binding covers these + // fields too; this check turns a griefing attempt into a failed transaction + // instead of a failed transaction plus a dead note. + match req.selector { + SELECTOR_TRANSFER if req.action_accounts != 0 => { + return Err(MirrorProgramError::MalformedInstruction.into()); + } + SELECTOR_TRANSFER | SELECTOR_INVOKE => {} + _ => return Err(MirrorProgramError::UnknownSelector.into()), + } let binding = mirror_core::action_binding( req.selector, &req.target_program, &req.beneficiary, req.relay_fee, + req.action_accounts, &req.payload, ); diff --git a/programs/mirror-pool/src/state.rs b/programs/mirror-pool/src/state.rs index 8f7dc570..6be13300 100644 --- a/programs/mirror-pool/src/state.rs +++ b/programs/mirror-pool/src/state.rs @@ -50,6 +50,16 @@ pub const POOL_LEN: usize = offset::END; /// reinterpreting their bytes. pub const POOL_VERSION: u8 = 1; +/// Smallest permitted anonymity floor. One is not a crowd. +pub const MIN_K_FLOOR: u32 = 2; + +/// Largest permitted anonymity floor: the tree's capacity. +/// +/// A floor above this can never be met, and since a pool is unique per +/// denomination and creation is permissionless, that would permanently deny the +/// protocol that denomination. +pub const MAX_K_FLOOR: u32 = 1 << TREE_DEPTH; + // Pin the layout. A field inserted in the middle would otherwise silently // reinterpret every deployed account. const _: () = assert!(offset::FILLED == 56); @@ -147,10 +157,25 @@ impl<'a> Pool<'a> { if denomination == 0 { return Err(MirrorProgramError::InvalidParameter); } - // A zero floor would let a lone member act with an anonymity set of one, - // which is worse than not using the pool at all: it advertises that a - // privacy tool was used while providing no cover. - if k_floor == 0 { + // The floor must be reachable, and it must actually be a crowd. + // + // Pool creation is permissionless and a pool is unique per denomination + // forever, so an unbounded floor is a griefing vector rather than a + // configuration mistake: a floor above the tree's capacity makes every + // spend fail permanently, and because no second pool for that + // denomination can ever exist, every later depositor loses their deposit + // with no recovery. Costing a fraction of a SOL, that would deny the + // protocol one denomination at a time. + // + // MIN_K_FLOOR is 2 because a floor of one is the anonymity set of one + // this check exists to prevent — it advertises that a privacy tool was + // used while providing no cover. + if !(MIN_K_FLOOR..=MAX_K_FLOOR).contains(&k_floor) { + return Err(MirrorProgramError::InvalidParameter); + } + // An entry fee at or above the denomination costs more to join than the + // note is worth. + if entry_fee >= denomination { return Err(MirrorProgramError::InvalidParameter); } @@ -280,7 +305,11 @@ mod tests { let mut data = blank(); { let mut pool = Pool::load_uninitialised(&mut data).unwrap(); - pool.initialise(254, 253, denomination, 1_000, 4).unwrap(); + // The fee has to stay below the denomination, so scale it rather + // than hardcoding one that only suits large pools. + let entry_fee = denomination / 100; + pool.initialise(254, 253, denomination, entry_fee, 4) + .unwrap(); } data } @@ -292,7 +321,7 @@ mod tests { assert_eq!(pool.bump(), 254); assert_eq!(pool.vault_bump(), 253); assert_eq!(pool.denomination(), 100_000); - assert_eq!(pool.entry_fee(), 1_000); + assert_eq!(pool.entry_fee(), 1_000); // 1% of 100_000 assert_eq!(pool.k_floor(), 4); assert_eq!(pool.deposit_count(), 0); assert_eq!(pool.spend_count(), 0); @@ -367,6 +396,47 @@ mod tests { )); } + /// A floor nobody can reach is a permanent denial of that denomination, + /// because pools are unique per denomination and creation is permissionless. + /// A griefer paying one pool's rent would otherwise lock out every honest + /// depositor for that size, forever, and take their deposits with it. + #[test] + fn an_unreachable_anonymity_floor_is_refused() { + let mut data = blank(); + let mut pool = Pool::load_uninitialised(&mut data).unwrap(); + assert!(matches!( + pool.initialise(1, 1, 1_000_000, 0, u32::MAX), + Err(MirrorProgramError::InvalidParameter) + )); + assert!(matches!( + pool.initialise(1, 1, 1_000_000, 0, MAX_K_FLOOR + 1), + Err(MirrorProgramError::InvalidParameter) + )); + assert!(pool.initialise(1, 1, 1_000_000, 0, MAX_K_FLOOR).is_ok()); + } + + #[test] + fn a_floor_of_one_is_refused_because_one_is_not_a_crowd() { + let mut data = blank(); + let mut pool = Pool::load_uninitialised(&mut data).unwrap(); + assert!(matches!( + pool.initialise(1, 1, 1_000_000, 0, 1), + Err(MirrorProgramError::InvalidParameter) + )); + assert!(pool.initialise(1, 1, 1_000_000, 0, MIN_K_FLOOR).is_ok()); + } + + #[test] + fn an_entry_fee_worth_more_than_the_note_is_refused() { + let mut data = blank(); + let mut pool = Pool::load_uninitialised(&mut data).unwrap(); + assert!(matches!( + pool.initialise(1, 1, 1_000, 1_000, 4), + Err(MirrorProgramError::InvalidParameter) + )); + assert!(pool.initialise(1, 1, 1_000, 999, 4).is_ok()); + } + #[test] fn the_root_ring_remembers_and_then_forgets() { let mut data = initialised(1); diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index e570bb01..97736f05 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -246,6 +246,7 @@ fn proof_for( &[0u8; 32], &beneficiary.to_bytes(), RELAY_FEE, + 0, &[], ); let witness = Witness { @@ -768,7 +769,7 @@ fn a_member_can_always_exit_without_any_relay() { let merkle_proof = tree.proof(0).unwrap(); let binding = - mirror_core::action_binding(SELECTOR, &[0u8; 32], &beneficiary.to_bytes(), 0, &[]); + mirror_core::action_binding(SELECTOR, &[0u8; 32], &beneficiary.to_bytes(), 0, 0, &[]); let witness = Witness { note: notes[0], merkle_proof: &merkle_proof, @@ -900,6 +901,7 @@ fn action_proof( &target.to_bytes(), &beneficiary.to_bytes(), RELAY_FEE, + 0, payload, ); let witness = Witness { @@ -1075,3 +1077,121 @@ fn an_action_cannot_re_enter_the_pool() { "expected SelfInvocationRefused: {err}" ); } + +/// The griefing vector an adversarial review found: `action_accounts` was +/// relay-supplied, written verbatim into the record, and outside the binding. +/// +/// A relay handed a valid transfer proof could submit it with a count that +/// settlement can never satisfy. The nullifier burns, the record cannot be +/// amended, there is no refund instruction, and the note is destroyed for free. +/// The relay forfeits only its fee. +#[test] +fn a_relay_cannot_declare_an_account_count_the_member_did_not_authorise() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + // Proved for a plain transfer, which takes no action accounts. + let proof = proof_for(&keys, &tree, ¬es, 0, &beneficiary); + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + + let ix = Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR, + target_program: [0u8; 32], + beneficiary: beneficiary.to_bytes(), + relay_fee: RELAY_FEE, + action_accounts: 1, // the member authorised zero + payload: Vec::new(), + } + .pack(), + vec![ + AccountMeta::new(relay.pubkey(), true), + AccountMeta::new(env.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let err = env + .send(ix, &relay) + .expect_err("a relay inflated the account count and burnt the note"); + println!("inflated action_accounts rejected: {err}"); + + // The nullifier must still be spendable: the griefing attempt cost the + // member nothing. + assert!( + env.svm.get_account(&spend_pda).is_none(), + "the spend record was created, so the note is now unspendable" + ); + let ok = spend_ix(&env, &proof, nullifier, &beneficiary, &relay.pubkey()); + env.send(ok, &relay) + .expect("the note must remain spendable after a failed griefing attempt"); +} + +#[test] +fn an_unknown_selector_is_refused_before_the_nullifier_burns() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + let merkle_proof = tree.proof(1).unwrap(); + let binding = + mirror_core::action_binding(99, &[0u8; 32], &beneficiary.to_bytes(), RELAY_FEE, 0, &[]); + let witness = Witness { + note: notes[1], + merkle_proof: &merkle_proof, + root: tree.root().unwrap(), + action_binding: binding, + }; + let mut rng = { + use ark_std::rand::SeedableRng; + ark_std::rand::rngs::StdRng::from_seed([77u8; 32]) + }; + let proof = prove(&keys, &witness, &mut rng).expect("proving"); + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + + let ix = Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: 99, + target_program: [0u8; 32], + beneficiary: beneficiary.to_bytes(), + relay_fee: RELAY_FEE, + action_accounts: 0, + payload: Vec::new(), + } + .pack(), + vec![ + AccountMeta::new(relay.pubkey(), true), + AccountMeta::new(env.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let err = env + .send(ix, &relay) + .expect_err("an unknown selector was stored"); + assert!( + err.contains("Custom(22)"), + "expected UnknownSelector: {err}" + ); + assert!( + env.svm.get_account(&spend_pda).is_none(), + "the nullifier burnt for a selector settlement can never execute" + ); +} From b09185cdf6bb2b0dfb74e90cf6d423eebd9e7f9e Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 17:07:43 -0300 Subject: [PATCH 26/83] test(program): exercise the action path with a non-empty account list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every action test passed zero accounts, so the loop that reads them and the metas built from them were dead code — in the submission's headline feature. Memo requires every account handed to it to have signed, so passing one makes the account list load-bearing: a dropped account, a mislabelled signer flag or a miscount now fails. Building that test found a real constraint. Appending the vault to the callee's account list breaks the runtime's balance check, because settlement has already moved the payout out of the vault by direct mutation and the runtime then reconciles those lamports across the CPI boundary. Marking it read-only does not help. So the vault authorises through its seeds and is never one of the callee's accounts, a settler that tries to place it there is refused, and THREAT_MODEL.md now states the consequence: an action whose target needs the pool itself as an account is not expressible in this version. Also documents that the binding covers how many accounts an action takes but not which — settlement is permissionless, so a settler picks them. For a target whose destination is an account rather than instruction data that is a real limit, and the README no longer implies otherwise. --- README.md | 13 +- data/sample-privacycash.json | 5175 +++++++++++++++++----- docs/THREAT_MODEL.md | 29 + programs/mirror-pool/src/processor.rs | 40 +- programs/mirror-pool/tests/end_to_end.rs | 166 +- 5 files changed, 4290 insertions(+), 1133 deletions(-) diff --git a/README.md b/README.md index 36ecb165..d7717f9c 100644 --- a/README.md +++ b/README.md @@ -68,12 +68,17 @@ epoch-scoped nullifier against a value payout, so one deposit pays out once per epoch forever. **A relay cannot redirect or re-price an action.** The action binding is never -transmitted — it is recomputed on-chain from the selector, the beneficiary and -the relay fee and used as the third public input, so altering any of them changes -the binding and the pairing fails. There is no separate field that could be -checked incorrectly. The test tampers with that exact input and asserts the real +transmitted — it is recomputed on-chain from the selector, the target program, +the beneficiary, the relay fee, the declared account count and the payload, then +used as the third public input, so altering any of them changes the binding and +the pairing fails. The test tampers with that exact input and asserts the real verifier rejects it. +It does **not** bind *which* accounts fill an action's slots, only how many. +Settlement is permissionless, so a settler chooses them; for a target whose +destination is an account rather than instruction data, that is a real limit and +`docs/THREAT_MODEL.md` states it. + **No member key ever appears on chain** on the relay path. **Nothing can hold a member's escrow.** There is no `self_spend` instruction diff --git a/data/sample-privacycash.json b/data/sample-privacycash.json index 9bdb7f5f..d4c1d4d2 100644 --- a/data/sample-privacycash.json +++ b/data/sample-privacycash.json @@ -1,294 +1,407 @@ { "manifest": { - "endpoint": "https://api.mainnet-beta.solana.com", + "endpoint": "https://solana-mainnet.g.alchemy.com", "first_available_block": 0, "archival_probe_ok": true, - "collected_at": 1784975925, + "collected_at": 1785004247, "config": { "scope": "Sol", - "depth_max": 5, + "depth_max": 8, "min_edge_lamports": 500000, - "sig_page_cap": 20, + "sig_page_cap": 60, "page_size": 1000 }, "thresholds_hub_signatures": 5000, "thresholds_distributor_fanout": 20, "thresholds_cluster_min_size": 3, - "rpc_calls": 627, + "rpc_calls": 4852, "excluded_non_wallet": [ - "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", - "AxG818LtyEa88dJDoHeYHW2WyNXrCLYUzr6ifL5qn4KB", - "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" + "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp", + "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB", + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY", + "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv", + "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis", + "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX", + "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG", + "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS", + "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA", + "2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha", + "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp", + "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH" ], - "ambiguous_attribution_rate": 0.2777777777777778 + "ambiguous_attribution_rate": 0.21428571428571427 }, "chains": [ { - "seed": "B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc", + "seed": "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", "visited": [ - "B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc" + "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], - "stop": "PageCapHit" + "stop": "RpcFailure" }, { - "seed": "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7", + "seed": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", "visited": [ - "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7", - "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "seed": "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k", "visited": [ - "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165" + "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k" ], - "stop": "PageCapHit" + "stop": "NoIncomingEdge" + }, + { + "seed": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", + "visited": [ + "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E" + ], + "stop": "RpcFailure" }, { - "seed": "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4", + "seed": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", "visited": [ - "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4", - "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8" + "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x", + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr", + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ", + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP", + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho", + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva" ], - "stop": "PageCapHit" + "stop": "DepthExceeded" }, { - "seed": "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", + "seed": "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", "visited": [ - "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", + "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z", + "seed": "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "visited": [ + "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" + ], + "stop": "RpcFailure" + }, + { + "seed": "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", "visited": [ - "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z", - "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi", + "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "seed": "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff", "visited": [ - "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff" ], "stop": "PageCapHit" }, { - "seed": "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u", + "seed": "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", "visited": [ - "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u", - "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L", - "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM", - "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9", + "seed": "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", "visited": [ - "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9", - "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd", - "G2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3t" + "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq", + "seed": "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v", + "visited": [ + "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", "visited": [ - "HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq" + "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd", + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC", + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7" ], "stop": "NoIncomingEdge" }, { - "seed": "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB", + "seed": "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "visited": [ + "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" + ], + "stop": "RpcFailure" + }, + { + "seed": "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", "visited": [ - "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB", - "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2", - "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS", - "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix", + "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", + "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ", + "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C", + "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", + "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB", + "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte", + "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an", + "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf", + "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM" + ], + "stop": "DepthExceeded" + }, + { + "seed": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "visited": [ + "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX", + "seed": "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf", "visited": [ - "DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX" + "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf" ], "stop": "NoIncomingEdge" }, { - "seed": "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M", + "seed": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", "visited": [ - "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M", - "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh", - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx", + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K", + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr" ], "stop": "PageCapHit" }, { - "seed": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "seed": "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", "visited": [ - "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT", + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1", + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "seed": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", "visited": [ - "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "visited": [ + "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", + "seed": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "visited": [ + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", "visited": [ - "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me", + "seed": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", "visited": [ - "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me", - "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb", - "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR", - "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB", - "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE", - "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb" + "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx" ], - "stop": "DepthExceeded" + "stop": "PageCapHit" }, { - "seed": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "seed": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", "visited": [ - "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", - "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", - "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" ], - "stop": "NoIncomingEdge" + "stop": "RpcFailure" }, { - "seed": "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", + "seed": "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", "visited": [ - "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", + "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", + "seed": "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", "visited": [ - "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E" + "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB" ], - "stop": "PageCapHit" + "stop": "LocalTerminal" }, { - "seed": "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", + "seed": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", "visited": [ - "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", + "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", + "seed": "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", "visited": [ - "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", + "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw", + "seed": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", "visited": [ - "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw", - "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG", + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7", + "seed": "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "visited": [ + "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "RpcFailure" + }, + { + "seed": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", "visited": [ - "BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7" + "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" ], "stop": "PageCapHit" }, { - "seed": "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo", + "seed": "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx", + "visited": [ + "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "visited": [ + "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV" + ], + "stop": "LocalTerminal" + }, + { + "seed": "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", "visited": [ - "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo", - "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L", - "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6", - "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug", - "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h", + "seed": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "visited": [ + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", "visited": [ - "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h", - "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s" + "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", + "seed": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", "visited": [ - "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM" + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3", + "seed": "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", "visited": [ - "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3", - "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q" + "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU", + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi" ], "stop": "PageCapHit" }, { - "seed": "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", + "seed": "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", "visited": [ - "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", + "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6", + "seed": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", "visited": [ - "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6", - "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW", - "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV" ], - "stop": "PageCapHit" + "stop": "RpcFailure" }, { - "seed": "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL", + "seed": "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", "visited": [ - "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL", - "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2" + "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", + "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q" ], "stop": "NoIncomingEdge" }, { - "seed": "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1", + "seed": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", "visited": [ - "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1", - "5otWRyzcb7D7DEGZoRJrn2RQ8mbGDDpWCntjh45fFKH1" + "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" ], - "stop": "LocalTerminal" + "stop": "PageCapHit" }, { "seed": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", @@ -300,315 +413,3157 @@ "stop": "PageCapHit" }, { - "seed": "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz", + "seed": "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U", "visited": [ - "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U" + ], + "stop": "RpcFailure" + }, + { + "seed": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "visited": [ + "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ" + ], + "stop": "LocalTerminal" + }, + { + "seed": "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "visited": [ + "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" ], "stop": "PageCapHit" }, { - "seed": "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", + "seed": "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", "visited": [ - "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "visited": [ + "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH", + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" ], "stop": "PageCapHit" }, { - "seed": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "seed": "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", "visited": [ - "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", - "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t" + ], + "stop": "RpcFailure" + }, + { + "seed": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "visited": [ + "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "visited": [ + "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "stop": "PageCapHit" }, { - "seed": "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP", + "seed": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", "visited": [ - "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP", - "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362", - "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw", - "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG", - "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU", - "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5" + "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" ], - "stop": "DepthExceeded" - } - ], - "facts": { - "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix": { - "address": "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix", - "owner": null, - "executable": false, - "signatures": { - "Exact": 189 - }, - "first_seen": 1784608146, - "birth_edge": { - "sink": "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix", - "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" - ], - "value": 153680469685, - "signature": "3ck9PBEzhy46jBLUWfaeoeEDLPFcC7wNpHzceyNWtzEte9HSiG3NafZb69iAZQHwAXpV5x6qFGWchcvH7QA4auJf", - "slot": 434235044, - "block_time": 1784608146, - "ambiguous_attribution": true - }, - "fee_payers": [ + "stop": "PageCapHit" + }, + { + "seed": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "visited": [ + "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", + "visited": [ + "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "visited": [ + "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], - "funded": [ - "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS" - ] + "stop": "PageCapHit" }, - "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy": { - "address": "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", - "owner": null, - "executable": false, - "signatures": { - "Exact": 0 - }, - "first_seen": null, - "birth_edge": null, - "fee_payers": [], - "funded": [] + { + "seed": "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "visited": [ + "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr" + ], + "stop": "PageCapHit" }, - "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz": { - "address": "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 20000 - }, - "first_seen": 1772954466, - "birth_edge": null, - "fee_payers": [], - "funded": [] + { + "seed": "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "visited": [ + "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" + ], + "stop": "RpcFailure" }, - "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM": { - "address": "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 2857 - }, - "first_seen": 1769191683, - "birth_edge": { - "sink": "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM", - "sources": [ - "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" - ], - "value": 4502430000, - "signature": "5wKGv3SouFHh7HBkxVeJiR1H2gmEv4k8tm8xMAwGLMSEe8yGWUZn8jE36GDyZTQQ3PyPV1YhhSR7ELpa3JXYY3cA", - "slot": 395454220, - "block_time": 1769191683, - "ambiguous_attribution": false - }, - "fee_payers": [ - "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + { + "seed": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "visited": [ + "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" ], - "funded": [ - "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L" - ] + "stop": "RpcFailure" }, - "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE": { - "address": "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 64 - }, - "first_seen": 1784607959, - "birth_edge": { - "sink": "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE", - "sources": [ - "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb" - ], - "value": 6000000000, - "signature": "2Bk6Vw6dmU8KDMvoyXeMbN5jmF7Rq2C2DkxMxjG4mkFvwfNFttNdAuPT5TkomyEipZ3Rbkh1tXHCACyKsgELM75N", - "slot": 434234594, - "block_time": 1784607959, - "ambiguous_attribution": false - }, - "fee_payers": [ - "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb" + { + "seed": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "visited": [ + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" ], - "funded": [ - "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB" - ] + "stop": "PageCapHit" }, - "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3": { - "address": "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 1274 - }, - "first_seen": 1766003500, - "birth_edge": { - "sink": "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3", - "sources": [ - "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q" - ], - "value": 5000000000, - "signature": "2rKL8XiedJnXjJc1CkwpiQSxST2KARnr3BbLMn9zVmJb617A61cj9NJah2QxJz4GiV82ER4vinr6KJqGRNKboW1k", - "slot": 387377211, - "block_time": 1766003500, - "ambiguous_attribution": false - }, - "fee_payers": [ - "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q" + { + "seed": "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "visited": [ + "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" ], - "funded": [] + "stop": "PageCapHit" }, - "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL": { - "address": "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 8 - }, - "first_seen": 1784717266, - "birth_edge": { - "sink": "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL", - "sources": [ - "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2" - ], - "value": 150000000, - "signature": "GQw3x4cEkSMcwjTFMoxpJj25Rx21VinHRVhn5bcqACf1VxXLE4tMpCcScGZYspCfe2MVostgEU4fqix9SwcgVHr", - "slot": 434494932, - "block_time": 1784717266, - "ambiguous_attribution": false - }, - "fee_payers": [ - "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2" + { + "seed": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "visited": [ + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" ], - "funded": [] + "stop": "PageCapHit" }, - "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr": { - "address": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", - "owner": "11111111111111111111111111111111", + { + "seed": "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "visited": [ + "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe", + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor", + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu", + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S", + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "visited": [ + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "visited": [ + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G" + ], + "stop": "PageCapHit" + }, + { + "seed": "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "visited": [ + "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o" + ], + "stop": "LocalTerminal" + }, + { + "seed": "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "visited": [ + "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "visited": [ + "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "visited": [ + "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "visited": [ + "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "visited": [ + "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "stop": "PageCapHit" + }, + { + "seed": "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "visited": [ + "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "visited": [ + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "visited": [ + "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB", + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx", + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S", + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr", + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh", + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm", + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94", + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf" + ], + "stop": "DepthExceeded" + }, + { + "seed": "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "visited": [ + "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "visited": [ + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "visited": [ + "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B", + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "stop": "PageCapHit" + }, + { + "seed": "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR", + "visited": [ + "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR" + ], + "stop": "PageCapHit" + }, + { + "seed": "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "visited": [ + "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn" + ], + "stop": "PageCapHit" + }, + { + "seed": "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "visited": [ + "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "visited": [ + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "visited": [ + "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "RpcFailure" + }, + { + "seed": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "visited": [ + "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "visited": [ + "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "visited": [ + "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" + ], + "stop": "RpcFailure" + }, + { + "seed": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "visited": [ + "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" + ], + "stop": "RpcFailure" + }, + { + "seed": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", + "visited": [ + "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM" + ], + "stop": "RpcFailure" + } + ], + "facts": { + "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E": { + "address": "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1784615427, + "birth_edge": { + "sink": "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 110900000, + "signature": "5e6e31Ja1kF2DbV7qrcSW9RZYywU7TpJfxr667CtkwBe4toXGeyZnYtdT8NJeQzM1fTpQULLkDXkraeVcYxArg8W", + "slot": 434252353, + "block_time": 1784615427, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [] + }, + "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma": { + "address": "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 607 + }, + "first_seen": 1783375005, + "birth_edge": { + "sink": "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 22913500000, + "signature": "2AkoeVHhVQwaNuxQEya9FmdWcho8YDPBUExnQwQ51LZCGAN81Xa6gmUiq5KufZD7VNapVbL566cGNQ54cHcQGDAZ", + "slot": 431247217, + "block_time": 1783375005, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z": { + "address": "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4277 + }, + "first_seen": 1647049347, + "birth_edge": { + "sink": "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", + "sources": [ + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" + ], + "value": 7244953950, + "signature": "419TeAWehDaugVDXtfT9GochWU2uzZfYE8Mj9KcRLxDtwZpVies6nT8ujSsqz5xcah5gEALomEeyeL5igrt1ZFeG", + "slot": 124535935, + "block_time": 1647049347, + "ambiguous_attribution": false + }, + "fee_payers": [ + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" + ], + "funded": [ + "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H" + ] + }, + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm": { + "address": "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1784282864, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH" + ] + }, + "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ": { + "address": "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 28 + }, + "first_seen": 1783722353, + "birth_edge": { + "sink": "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 12948500000, + "signature": "5rMkvoDiSrN16YHjCLekrebvkmsHxvssazWRbP2Fq5rZNhyyMcUTULMb136XBMrKVZzwRyhbj7qb3cx3sgGiWAoX", + "slot": 432098751, + "block_time": 1783722353, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha": { + "address": "2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9": { + "address": "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784475307, + "birth_edge": { + "sink": "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "sources": [ + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn", + "6tckHFBpiJ8YgYN8FUskvtvTpXQZ55g5LHeo1kvELoDQ" + ], + "value": 5010000000, + "signature": "4bs1AqpjFmhSM6Hgg7nGwN7FbytYnLRcGmiTMP6TCCRP2rhhT4hf8Bb62Jm7uTqxc4vs9scPgT3vbEb98uQfdNF3", + "slot": 433922248, + "block_time": 1784475307, + "ambiguous_attribution": true + }, + "fee_payers": [ + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn" + ], + "funded": [] + }, + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X": { + "address": "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1743262736, + "birth_edge": { + "sink": "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", + "sources": [ + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" + ], + "value": 779470319, + "signature": "2YjKzdqoKhck9hGc8VmXR5gxdnX1SPDG6WByy1Mm3tHz5rzESsiEf3TLLof8but3XH3XqMVX9ZHbvAR9fhVPanWM", + "slot": 329976683, + "block_time": 1743262736, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" + ], + "funded": [ + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu" + ] + }, + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V": { + "address": "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", + "owner": null, + "executable": false, + "signatures": { + "Exact": 249 + }, + "first_seen": 1776070891, + "birth_edge": { + "sink": "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", + "sources": [ + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ" + ], + "value": 600000000, + "signature": "2ifPXJE5e8zqjQTqTP7duLXvDyDjTsJrqcf5iEdywn3Z29p3Zsr3RcgYFXPkkxZMhMNg3HQyeVHSqQHhCrDAYnPM", + "slot": 412912638, + "block_time": 1776070891, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ" + ], + "funded": [ + "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU" + ] + }, + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy": { + "address": "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1574 + }, + "first_seen": 1784752160, + "birth_edge": { + "sink": "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "sources": [ + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" + ], + "value": 43881516584, + "signature": "3NEwDfuC73NVthq8zeasPw9Ud6qcWZiy65wFFZSWcbxP86UjAsc9ZPApAdjk7EYo88ptZcUZbSLdmQwZkgqSCcRc", + "slot": 434577818, + "block_time": 1784752160, + "ambiguous_attribution": false + }, + "fee_payers": [ + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" + ], + "funded": [ + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x" + ] + }, + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg": { + "address": "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12 + }, + "first_seen": 1781285770, + "birth_edge": { + "sink": "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "sources": [ + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G" + ], + "value": 1677937000, + "signature": "2TJSuukc2SNr9YL2qRHeNHmu37kfsE2cBnQkz7TJTxuRkVw48fefVYqxsGCV2ULsb1PnVvzmcQGJt5J7uouabgYZ", + "slot": 426027687, + "block_time": 1781285770, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G" + ], + "funded": [] + }, + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem": { + "address": "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", + "owner": null, + "executable": false, + "signatures": { + "Exact": 263 + }, + "first_seen": 1713736580, + "birth_edge": { + "sink": "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", + "sources": [ + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" + ], + "value": 12006763000, + "signature": "62g49CyyWTmom7Qxdnr64iSy7W8PDuxrZojz5q6ztpgYkM7WwwXkfcyeNVditM45iomtRpgqXUbLW8kXpScStkuL", + "slot": 261492401, + "block_time": 1713736580, + "ambiguous_attribution": false + }, + "fee_payers": [ + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" + ], + "funded": [ + "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq" + ] + }, + "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf": { + "address": "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 32562 + }, + "first_seen": 1729284419, + "birth_edge": null, + "fee_payers": [ + "gpoo1atPkrKnfxQ4Qt214ErbgBBJeiksL1EjqBHynbo" + ], + "funded": [] + }, + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz": { + "address": "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1756855300, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38" + ] + }, + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS": { + "address": "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1782422249, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B" + ] + }, + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1": { + "address": "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 100 + }, + "first_seen": 1784818359, + "birth_edge": { + "sink": "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1", + "sources": [ + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7" + ], + "value": 6000000000, + "signature": "2StbhyBVwhP2wRhTYMM31x5qU3B2WBr1jFXx6KD6CGeXp9xpWQqN7RKoxa98X86h8v6SGprqMdi9CVGh9PLSjAQH", + "slot": 434735789, + "block_time": 1784818359, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7" + ], + "funded": [ + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT" + ] + }, + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr": { + "address": "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1784752151, + "birth_edge": { + "sink": "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr", + "sources": [ + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ" + ], + "value": 45278992944, + "signature": "LnCoQVyRCZyi9ktCp3JyZPKhGrTu19FzpmhP7SjvQ8TKYS7uu9dPE23AuzRQz32YWqaTQG9rabCkoAihw2swguS", + "slot": 434577797, + "block_time": 1784752151, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ" + ], + "funded": [ + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" + ] + }, + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt": { + "address": "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784444104, + "birth_edge": { + "sink": "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", + "sources": [ + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" + ], + "value": 5986856990, + "signature": "3k3C9SKrmZZ9aKwV75WzsqDnXRj1jnrfVoq6W5E4wxyjcAH7ZGiMbrDKVqStRdtpFm4Y2vzYA8WvEyUdSs67b1UN", + "slot": 433847718, + "block_time": 1784444104, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" + ], + "funded": [ + "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH" + ] + }, + "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL": { + "address": "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784544632, + "birth_edge": { + "sink": "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "sources": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "value": 928815670, + "signature": "85PNojTrpgdkLHWP1CQRGD6G6CttqKUCr5zYDjLXkFsB4teH5D9kcZqZjTAWX3ydRqfVhwLAKLzC6zYsU3K1A8L", + "slot": 434085334, + "block_time": 1784544632, + "ambiguous_attribution": false + }, + "fee_payers": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "funded": [] + }, + "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H": { + "address": "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 528 + }, + "first_seen": 1734052375, + "birth_edge": { + "sink": "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "sources": [ + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z" + ], + "value": 1020000000, + "signature": "4Mm83Hp6ciDRNzM3tSFJt6AyoXPc7kD4moyqFobEtqzXDAnC2k5hNt83wNGSsn61KQjaNdyYYqWHcJjaUA9qubwX", + "slot": 307120184, + "block_time": 1734052375, + "ambiguous_attribution": false + }, + "fee_payers": [ + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z" + ], + "funded": [] + }, + "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH": { + "address": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 277 + }, + "first_seen": 1784444332, + "birth_edge": { + "sink": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "sources": [ + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt" + ], + "value": 2985933011, + "signature": "4e1bYdz4z6QTzWFbSbjFRJpDdhuc2m2zaopi6x8N1w81fa6352rDi2VCzqyxKdRuvPKthVeikZXGj91o6bvCfeK4", + "slot": 433848260, + "block_time": 1784444332, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt" + ], + "funded": [] + }, + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr": { + "address": "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1775469990, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh" + ] + }, + "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7": { + "address": "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784462394, + "birth_edge": { + "sink": "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 90000000, + "signature": "4jhAt2GJwyGRfe5sJkFB2HVjnUzLi86PVfucCvUM6u3JFRwegv57f4ZkhmRH6huyS8pNEZa8nbPs9Kb6PtXYUAqz", + "slot": 433891372, + "block_time": 1784462394, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu": { + "address": "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1764 + }, + "first_seen": 1781821319, + "birth_edge": { + "sink": "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu", + "sources": [ + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ" + ], + "value": 400000000, + "signature": "t439hwXMQP6B4Yk83sUHquHMZR9q8bqQK4na6cWaxbvVCnQKw5hzceNSBXCJKRmcEzmJBFLd19bfcWsSDKGxhU6", + "slot": 427377732, + "block_time": 1781821319, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ" + ], + "funded": [ + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor" + ] + }, + "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv": { + "address": "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze": { + "address": "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 695 + }, + "first_seen": 1784674265, + "birth_edge": { + "sink": "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 6040700317, + "signature": "3tGju22hVCi5Tn5mQv65Uokr3ahGwzYsU5urYkse9Bq4SgSRPmwiU8FzhX51HBdMCKyTor6i8G1uc2LnhHge4uRb", + "slot": 434392522, + "block_time": 1784674265, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9": { + "address": "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", + "owner": null, + "executable": false, + "signatures": { + "Exact": 42 + }, + "first_seen": 1682118364, + "birth_edge": { + "sink": "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", + "sources": [ + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" + ], + "value": 11742708040, + "signature": "4fDKVyLL5y4Kzuedc8MGwhkkXd1oWbkMqBZTsHtc1H7NFTWijug7Lciv42HG6VkxfjZSemzGn6fCysTTxQPG2Zsb", + "slot": 189742975, + "block_time": 1682118364, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" + ], + "funded": [ + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" + ] + }, + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr": { + "address": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9783 + }, + "first_seen": 1781265894, + "birth_edge": { + "sink": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 209692102518, + "signature": "2WnT866FtZdcXqRvfQoypXGhWSQRtqa4cCg453yjkGSuZA9fs8LJviugPbbGP9GYu3TDJhohtJeDwq1AuoeqJBFi", + "slot": 425977688, + "block_time": 1781265894, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16": { + "address": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9762 + }, + "first_seen": 1772531584, + "birth_edge": { + "sink": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "sources": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "value": 302066170, + "signature": "5A3SzsgcHRLJQUVBqDNdCW7M8XnW6jS7TBjiuERozS7a5YAXc4EieN5d58j5Ts7mqNssUsZZj3TKUTNAett3gqgK", + "slot": 403914286, + "block_time": 1772531584, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "funded": [] + }, + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w": { + "address": "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1783188037, + "birth_edge": { + "sink": "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 100528380, + "signature": "5CdkjWty9v98DDGJPREJK7UpeGUaaBdQxo87kDxyqajMtFtbnzVuL2h9UCu4Ue4iVqu4vWgiBLbs8XCjnAswLrDR", + "slot": 430784309, + "block_time": 1783188037, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [ + "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k" + ] + }, + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk": { + "address": "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1013 + }, + "first_seen": 1781819169, + "birth_edge": { + "sink": "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 25895028000, + "signature": "5e4gkh2xn451jv5rnKUJN81VDUN9bJTY3VESq2eQ7ACZJ51DuXZMFBaat5X5tVe5VBLzSnGbKri4KnpFezdqG3mu", + "slot": 427372317, + "block_time": 1781819169, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S" + ] + }, + "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd": { + "address": "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 86 + }, + "first_seen": 1784917747, + "birth_edge": { + "sink": "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 3063734792, + "signature": "5iF2hZ9Pg77skU74ve2HwpkKpHnSjw2W13DmC67uSo2FAFaUC4FSLEZvv2BSwjwtF1NTfCVuBRKbghGoqpmtUmd7", + "slot": 434972545, + "block_time": 1784917747, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte": { + "address": "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784839342, + "birth_edge": { + "sink": "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte", + "sources": [ + "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an" + ], + "value": 2277601434, + "signature": "y43VQ2DHo3WkeWopaAYCfp95qN2cpZnwP5mstF1f8UopGZyJAwuaSYcrCyjg3xt8j15CyMxMn6aFztyYLBkjKHV", + "slot": 434785674, + "block_time": 1784839342, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an" + ], + "funded": [ + "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB" + ] + }, + "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ": { + "address": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 17 + }, + "first_seen": 1784823865, + "birth_edge": { + "sink": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "sources": [ + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" + ], + "value": 26325656111, + "signature": "4Fvr5kGDCyhHU3xmpCJvMRy4YAGLs93UAu2mbCG3V39pu9ASyDAqEeMqve3CmwgDUieLGp4q6uoGvFHWBmqt2hvK", + "slot": 434748892, + "block_time": 1784823865, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" + ], + "funded": [] + }, + "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr": { + "address": "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 11 + }, + "first_seen": 1784836721, + "birth_edge": { + "sink": "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 5496619887, + "signature": "XXqgPe1F8j4D89jD1dvd4qP8LDpxJgxyZ7yvGTR6cpNAPz8tfgeypBdBFVUrrBVFwRiAqVq9Sv51b4hYUVWDdM2", + "slot": 434779463, + "block_time": 1784836721, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr": { + "address": "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894220, + "birth_edge": { + "sink": "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr", + "sources": [ + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh" + ], + "value": 849094919, + "signature": "4MJQDBRmFT2jFhKZx7rY84rz4GThjUhU2twqk6URjTrCx1k6SQwZd4toBBR6bKnqS7GvPowPP17GnwJw8DGFqviH", + "slot": 394707768, + "block_time": 1768894220, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh" + ], + "funded": [ + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S" + ] + }, + "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS": { + "address": "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za": { + "address": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 515 + }, + "first_seen": 1784934334, + "birth_edge": { + "sink": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "sources": [ + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x" + ], + "value": 52977372793, + "signature": "4JY37vAAtVC9a5sQn4z8CY5mRJ5t8njqiBziNK9Ps8QtpG9m1qPsfCLFFo8HuGFjdPNTUtDaLQ9v2TF1auyUDWg7", + "slot": 435012131, + "block_time": 1784934334, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x" + ], + "funded": [] + }, + "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p": { + "address": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1783475831, + "birth_edge": { + "sink": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "sources": [ + "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ" + ], + "value": 388393719000, + "signature": "5imCWUcQWCKj7vDdLUbDXDeE567zN2Eg4Le8AWi6v7f1ZnsbtijSC6i65rA6BpVCsK9kc9fRrBwF4jGBARwWPf1m", + "slot": 431495612, + "block_time": 1783475831, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BrGihgGjCmpu2p96rou6GQj3sQpwgeud937jf4RvZP9G" + ], + "funded": [] + }, + "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3": { + "address": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1784433292, + "birth_edge": { + "sink": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 669228400, + "signature": "66Tc5f3Uko3nDvDySXyoV4rhTBSka9cPczYiReZ8upWdrXKNhRGUHTTsZizm9mdNDEYdTkUmfuynxX3QEPjthemd", + "slot": 433821901, + "block_time": 1784433292, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV": { + "address": "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u" + ] + }, + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9": { + "address": "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1784762565, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW" + ] + }, + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA": { + "address": "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 15633 + }, + "first_seen": 1739951269, + "birth_edge": { + "sink": "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", + "sources": [ + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" + ], + "value": 3000000000, + "signature": "2GUhQXYjQ56ULyk5kFQ5pfduXNtqXhiikEMcLeZBaVDu2zrQfTDkt9bpVV6ETHzmmXaJn7oyGxu73eK9FqtRTF3g", + "slot": 321645004, + "block_time": 1739951269, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" + ], + "funded": [ + "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF" + ] + }, + "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7": { + "address": "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", + "owner": null, + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784924107, + "birth_edge": { + "sink": "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", + "sources": [ + "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ" + ], + "value": 2234728114, + "signature": "2KUUfihEsLasy4LETawwKcw9pk4c5yNPhirb3kSWrsRcvitubDvSpAL9Mr4CFjKbM4qT6g3FjHx69zwE6L6Rh1qG", + "slot": 434987740, + "block_time": 1784924107, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ" + ], + "funded": [] + }, + "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk": { + "address": "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 142 + }, + "first_seen": 1784420501, + "birth_edge": { + "sink": "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1488750000, + "signature": "5reLwzaWen3xeyYobmDJv4AWkboVwkmQea8aqnBXo6AW86tVotCN8aD4HVUseVngB1i8Nz7bWPVayfkXMjq2NZWd", + "slot": 433791379, + "block_time": 1784420501, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S": { + "address": "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894225, + "birth_edge": { + "sink": "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S", + "sources": [ + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr" + ], + "value": 849089919, + "signature": "5mpXMBqiqCUXVfjKe2fqnfr1yXBFt2MnSb5sU4PhDCru1KLnWw7Aji9tVP8ywUoP2X2yAAsgDnLsybmSY33VB9LJ", + "slot": 394707780, + "block_time": 1768894225, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr" + ], + "funded": [ + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx" + ] + }, + "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq": { + "address": "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 54 + }, + "first_seen": 1784676427, + "birth_edge": { + "sink": "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 49804000, + "signature": "2ksPaRqePKa3tLNgb6dfJ4xGxBvHsRJuNXeikfUEuupy8NvcTgf3AL8cq7AZRYALiWDeKz2BWxhNZdDHxdVEoDQg", + "slot": 434397667, + "block_time": 1784676427, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx": { + "address": "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1784813668, + "birth_edge": null, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [] + }, + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY": { + "address": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19394 + }, + "first_seen": 1774644767, + "birth_edge": { + "sink": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "sources": [ + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv" + ], + "value": 5972990500, + "signature": "2FGgEReXPwdXR3S6e4R5wxsotsqmfJU3EEC2QmGDtQF5WxGAspSae7s8GWY2FGx4o9u1oyWSsWMrPznZdTD7KNkn", + "slot": 409284086, + "block_time": 1774644767, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv" + ], + "funded": [] + }, + "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB": { + "address": "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT" + ] + }, + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S": { + "address": "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1310 + }, + "first_seen": 1781821301, + "birth_edge": { + "sink": "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S", + "sources": [ + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk" + ], + "value": 600000000, + "signature": "3B7AsVriGEHPW4TRFBkaFhV6YD4RDZ1w7g4UwNtT7psQb95ZMifH6v4QDUwL1k42tLpqhGqiExaje5LTEMkMBwQ8", + "slot": 427377690, + "block_time": 1781821301, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk" + ], + "funded": [ + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ" + ] + }, + "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB": { + "address": "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz": { + "address": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 963 + }, + "first_seen": 1774473115, + "birth_edge": { + "sink": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 34895500000, + "signature": "3omGYaNeXk9VjMv3rTp9aoaJrkED6b9jTiHoPx4UbRupiWmUn7op8udTBwguzph7YGJWQpNEhnv1FFDskSRsZANh", + "slot": 408844844, + "block_time": 1774473115, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw": { + "address": "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 277 + }, + "first_seen": 1780094619, + "birth_edge": { + "sink": "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 2991500000, + "signature": "84Spr3goYUhDoyS5JCevCc1nDRjo6vWCWeKajzmd3qHWAmsNkBoEjw8322ajWKS5GJyVShK56h1ZUh2VdxtBeCz", + "slot": 423031442, + "block_time": 1780094619, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ" + ] + }, + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme": { + "address": "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1712133901, + "birth_edge": { + "sink": "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", + "sources": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "value": 981006000, + "signature": "2718Lqzzrche69Pqhrt41GqkDBghW5SrqpxVVjrYV5fDNZ2tdipmhyYVKaXEj7Fex4h5mF5NMJ5QU8ohazEukjZG", + "slot": 258093459, + "block_time": 1712133901, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "funded": [ + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW" + ] + }, + "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF": { + "address": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1758995033, + "birth_edge": { + "sink": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "sources": [ + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" + ], + "value": 10000000, + "signature": "5eJNQ4bhKNQYiTVSpUhrHjGpFSs7Ft4GKLrJWqAK5BTnkuzd1xqVV8P1FcpXKG6Zb77U9qdDkyVN6gz1UYzoeB1T", + "slot": 369652410, + "block_time": 1758995033, + "ambiguous_attribution": false + }, + "fee_payers": [ + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" + ], + "funded": [] + }, + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW": { + "address": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11302 + }, + "first_seen": 1728079811, + "birth_edge": { + "sink": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "sources": [ + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" + ], + "value": 200000000, + "signature": "4rvwJT124GYcZAHHJJBqaZ9YRgnxYLF4nQ58wLbJSr1Z9BgH2NXyKvMRRCbN7CUWooZ9BMNPYAACSwPAqq7NgLvt", + "slot": 293725783, + "block_time": 1728079811, + "ambiguous_attribution": false + }, + "fee_payers": [ + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" + ], + "funded": [] + }, + "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an": { + "address": "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784834874, + "birth_edge": { + "sink": "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an", + "sources": [ + "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf" + ], + "value": 2277606434, + "signature": "4Mr6i7VrAqcRcoLitHbNH9CCE2siwMAfUmiZKnc9qBcrawcoa6gU5KwiRUdHP65JbqcdH4szA5EgjRqCCyDtSNJZ", + "slot": 434775083, + "block_time": 1784834874, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf" + ], + "funded": [ + "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte" + ] + }, + "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG": { + "address": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784565160, + "birth_edge": { + "sink": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "sources": [ + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob" + ], + "value": 232000000000, + "signature": "28bt62zYkiihXsM3o7rp2D6YS63G8CpZwUzpqqp2b8jNzGoUih1PWx9FGMdwfQFAEdRGTgz15pPQ8j9uZgDeyvw8", + "slot": 434133197, + "block_time": 1784565160, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob" + ], + "funded": [] + }, + "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ": { + "address": "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784924103, + "birth_edge": { + "sink": "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ", + "sources": [ + "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C" + ], + "value": 2234733114, + "signature": "4kpN8LZhHDgAUeceaUxSpUKzCSLaJLBYUVbUWwn7b8TeEzJxwUjmhxRuZs7ggZQiTMFcm1zKXhZ4sSDdzYJcV2eE", + "slot": 434987732, + "block_time": 1784924103, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C" + ], + "funded": [ + "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7" + ] + }, + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j": { + "address": "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 72 + }, + "first_seen": 1682118021, + "birth_edge": { + "sink": "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", + "sources": [ + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + ], + "value": 11744752320, + "signature": "pALYeH3rq18atGAUHgbqmMBXckY34TfyqKywTwxPzmeg23rXbtbqW9mf3NMct3WsizeZszfuwskkXvbGefvGRRA", + "slot": 189742217, + "block_time": 1682118021, + "ambiguous_attribution": false + }, + "fee_payers": [ + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + ], + "funded": [ + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" + ] + }, + "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG": { + "address": "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP": { + "address": "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP", + "owner": null, + "executable": false, + "signatures": { + "Exact": 999 + }, + "first_seen": 1784462436, + "birth_edge": { + "sink": "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP", + "sources": [ + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho" + ], + "value": 49648773129, + "signature": "3xd1Te2qtTYHjh2ohyorXsFLgxgWxaveB9tfyGzszJf9MmM1XHhFzQoi5C5ftnUWhXMPRvJpBuHpFidFgZVh2i3g", + "slot": 433891472, + "block_time": 1784462436, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho" + ], + "funded": [ + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm" + ] + }, + "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ": { + "address": "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 32 + }, + "first_seen": 1784389227, + "birth_edge": { + "sink": "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "sources": [ + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb" + ], + "value": 807208726, + "signature": "3vny3LuBCtnjHmNBUw4YGWxGiJdB6MDgCTTwB85ixPF8urpfbhjUiR3DJvptVkoB82k8MLKxgudAvq8m5CPLHCG", + "slot": 433716525, + "block_time": 1784389227, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb" + ], + "funded": [] + }, + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH": { + "address": "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1705 + }, + "first_seen": 1715894645, + "birth_edge": { + "sink": "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH", + "sources": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "value": 106710780, + "signature": "xDgEvEFQw8Qer5vQHtG755NyT2FHXPnFV9CPvhuvZn4NfZ3b5tWrUL5wDcGVyBDVSSA13r7Y6Tgtdp2mjrmHZrH", + "slot": 266193828, + "block_time": 1715894645, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "funded": [ + "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo" + ] + }, + "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH": { + "address": "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ": { + "address": "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1685627573, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" + ] + }, + "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u": { + "address": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784630445, + "birth_edge": { + "sink": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "sources": [ + "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV" + ], + "value": 6831797746, + "signature": "4V6TatwrgYYdAXsbYZ58LgACqMKm51JVfQ6cgiJvjtXwvz8NPjZbhsrz9j2QvwKXXv18bkkgtJScAaw5wZy5sKZk", + "slot": 434288158, + "block_time": 1784630445, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "funded": [] + }, + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7": { + "address": "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7", + "owner": null, + "executable": false, + "signatures": { + "Exact": 176 + }, + "first_seen": 1731698151, + "birth_edge": null, + "fee_payers": [ + "4ritJBeodrzcSDDeAJbKafJniczjYhAfsoZRXTQq9h82" + ], + "funded": [ + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC" + ] + }, + "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT": { + "address": "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784049521, + "birth_edge": { + "sink": "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "sources": [ + "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB" + ], + "value": 2110033428, + "signature": "1tK2a5Q2E19ZBWkhkhMDf36cuv8NZD8pcRFbVhxkR4kedkEjLsxS7dD1QsKHuZ1T77XRbGiWQx9fnQWcDtgLQmD", + "slot": 432895720, + "block_time": 1784049521, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH": { + "address": "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784564457, + "birth_edge": { + "sink": "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 156610510360, + "signature": "3HmDQGGFDNXPquCgrBn4oFueH8EcudAUohYxWiRVekmuX53cZhFotqNnLLPzUEeBnnhHikzZWtUwuP2M6K8EZq1L", + "slot": 434131562, + "block_time": 1784564457, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ": { + "address": "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p" + ] + }, + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx": { + "address": "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1782513070, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC" + ] + }, + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1": { + "address": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2154 + }, + "first_seen": 1684141322, + "birth_edge": { + "sink": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "sources": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "value": 90000000, + "signature": "52ZPH7cfAzwauMYh529GgHWhTzRwReNdJyWYr5ZugsAB5gvSmP6CYuCV1U79B9SLxLjk6qy5nsDUmao8Xa5y5EBB", + "slot": 194024003, + "block_time": 1684141322, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "funded": [ + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6" + ] + }, + "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1": { + "address": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5535 + }, + "first_seen": 1747156899, + "birth_edge": { + "sink": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "sources": [ + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" + ], + "value": 1000000000, + "signature": "2cATjnRUeA8sBSjhU4udUTHwqFk5cLQfx2mMZ7isJfKvdfGEJNn4q3Ex72Ww3fRNX7JUQTGpy25R6FVdfFwRP5dV", + "slot": 339786892, + "block_time": 1747156899, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" + ], + "funded": [] + }, + "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb": { + "address": "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1356 + }, + "first_seen": 1778270115, + "birth_edge": { + "sink": "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 31543190000, + "signature": "23eCmHrxuyS6rMmjNNDsJtUZk4vesD5maoraeVjWmqKuLe1yeuELQgEDaqfBDUWSTrkM3iuQFUWAsPpkjAiD6meJ", + "slot": 418463518, + "block_time": 1778270115, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr": { + "address": "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1765047135, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" + ] + }, + "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C": { + "address": "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784924100, + "birth_edge": { + "sink": "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C", + "sources": [ + "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4" + ], + "value": 2234738114, + "signature": "C2MVia7U24udidRDGAp7RwyB4WZL2u19NN6pW4URMxVCpPf5HFoxQ7uYnJKRDw8jC3qKxrD1m87iW57h3DiVidt", + "slot": 434987723, + "block_time": 1784924100, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4" + ], + "funded": [ + "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ" + ] + }, + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu": { + "address": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20506 + }, + "first_seen": 1743263000, + "birth_edge": { + "sink": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "sources": [ + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" + ], + "value": 770000000, + "signature": "rWZNiWUUErqSurTeKbooG5bPJgChg9SvYyYCsgNUy6AXuiHTsyVDYCyzCmYssFrs94PhG2ok9FwYAkVEDrK6efw", + "slot": 329977339, + "block_time": 1743263000, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" + ], + "funded": [] + }, + "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu": { + "address": "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 428 + }, + "first_seen": 1783530592, + "birth_edge": { + "sink": "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "sources": [ + "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o" + ], + "value": 3608900737, + "signature": "39zbFyzcd2pWwLEQBD1gB1qWrjTekKNiicL8TpyjTAoyLABpT518QVo9WVz8yxdzCJF1WiRcvuHzJRR85WFBXjfv", + "slot": 431630423, + "block_time": 1783530592, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn": { + "address": "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784565684, + "birth_edge": { + "sink": "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 841356800, + "signature": "4Y2A99s86tDM9Ef8DQ62ED34YhLejSy9dfkYDHR2rAi6kcnSNvg5pW1F83RyJuHK4DPoxkN5mAkvqvyA1EHJj4Wd", + "slot": 434134426, + "block_time": 1784565684, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [] + }, + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh": { + "address": "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894217, + "birth_edge": { + "sink": "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh", + "sources": [ + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" + ], + "value": 849099919, + "signature": "4UhWatoFQvHLdrg56s4qh3NjJaa9jpsS55YeGbi2FwyNHuoqfnZBLZx3npfoyjhFbvMaNm5vxYmYSAkjPdK2Gioi", + "slot": 394707760, + "block_time": 1768894217, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" + ], + "funded": [ + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr" + ] + }, + "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp": { + "address": "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 205 + }, + "first_seen": 1775579392, + "birth_edge": { + "sink": "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 9131905000, + "signature": "3zWVxUN5PpQhBBHTb7q2iVdGYZWzYX96QFS1nC467YtQBSKuqjZzLFQEPh816DEVyZmtn6xdFvssDpQqdopjccPh", + "slot": 411667852, + "block_time": 1775579392, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi": { + "address": "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1776006611, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU" + ] + }, + "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY": { + "address": "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR": { + "address": "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1783634671, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL" + ] + }, + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW": { + "address": "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1777580620, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", + "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM" + ] + }, + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx": { + "address": "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894233, + "birth_edge": { + "sink": "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx", + "sources": [ + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S" + ], + "value": 849084919, + "signature": "4MQSbWiBDWsVRVVKMjprc9fMfevDFzvAJJDcTd7PPwkGKXTfqJtB5frc9naHWzJv1S5yi3XLnmKZPMEbQx95M7aU", + "slot": 394707800, + "block_time": 1768894233, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S" + ], + "funded": [ + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB" + ] + }, + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ": { + "address": "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1784138101, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16" + ] + }, + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW": { + "address": "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW", + "owner": null, + "executable": false, + "signatures": { + "Exact": 711 + }, + "first_seen": 1663704019, + "birth_edge": { + "sink": "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 3079735480, + "signature": "24tTRqCdoMDLxwi41jhddFdaqydjZowSRXZHW2xuBqNttskX1ej72PhQUDqGoiF9jY5u9i8kbdyMcx5MMUrRWXsD", + "slot": 151573078, + "block_time": 1663704019, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq" + ] + }, + "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM": { + "address": "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784834865, + "birth_edge": { + "sink": "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM", + "sources": [ + "7kDQio2u675o7WXC9o9kdzHgiKKHv9E7ysbsNm4JaPvJ" + ], + "value": 2277616434, + "signature": "J5PgLjAoHnCZhNCufCPnDdRDM8jCpkJWtFPW96KXEhdNj417PzakjcuZdzVubT4WU5pL4uPjdfuCERJMe1MZ7HA", + "slot": 434775064, + "block_time": 1784834865, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7kDQio2u675o7WXC9o9kdzHgiKKHv9E7ysbsNm4JaPvJ" + ], + "funded": [ + "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf" + ] + }, + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ": { + "address": "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2412 + }, + "first_seen": 1781821309, + "birth_edge": { + "sink": "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", + "sources": [ + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S" + ], + "value": 500000000, + "signature": "4wVPQe58DmcHwkdVmHuvqWFGnQZhQ4ndiumpcrvNSYx6bL2bBCk6CCNeWpKDPzvc8Y68RpfFKuCrfScRuL2VaE58", + "slot": 427377712, + "block_time": 1781821309, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S" + ], + "funded": [ + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu" + ] + }, + "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo": { + "address": "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1784736131, + "birth_edge": { + "sink": "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", + "sources": [ + "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q" + ], + "value": 350000000, + "signature": "jGbgbqcTo3B2ukJHtnSqyRXGdLfbcVei8RMZPRQFLUkL7X7aa8pA8E3zRpEcYjpF2SYDLApH6FSfCw79eeW9g5w", + "slot": 434539781, + "block_time": 1784736131, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q" + ], + "funded": [] + }, + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX": { + "address": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 16358 + }, + "first_seen": 1774739788, + "birth_edge": { + "sink": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 3440555488, + "signature": "3YjE4YpSZT1wXeSk57dn8UrLTMQEWkLqF2r7LpApgEvjMt2h6k5GrkEQfeu4JtsHkPZJvVKqGZkhzKeDxFvroTJj", + "slot": 409527481, + "block_time": 1774739788, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ": { + "address": "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 107 + }, + "first_seen": 1783458420, + "birth_edge": null, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ": { + "address": "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 618 + }, + "first_seen": 1774728729, + "birth_edge": { + "sink": "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", + "sources": [ + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" + ], + "value": 11650417543, + "signature": "5TnbPcr9zLALAWF82aL7PVBNbX7PkaUTzdXgAWEnvZAeE8n5cYzXhyEKh438w9uj6LrF316rj73McA84g5DHueHM", + "slot": 409499170, + "block_time": 1774728729, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" + ], + "funded": [ + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V" + ] + }, + "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG": { + "address": "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 6 + }, + "first_seen": 1784519630, + "birth_edge": { + "sink": "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "sources": [ + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB" + ], + "value": 104889883544, + "signature": "5162f689Bxmi9PA5Fj9QVxBuxjHrWqbJnoHVVjnmUg4HB9j8XnMvUmLaugVAcyEnAbpiPQNQiukSiE2D2H2AUKaQ", + "slot": 434026767, + "block_time": 1784519630, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB" + ], + "funded": [] + }, + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC": { + "address": "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1748177913, + "birth_edge": { + "sink": "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC", + "sources": [ + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7" + ], + "value": 1029000000, + "signature": "CfG62TFDb4cuNH7FNWz4Cr2Ga14r88NoTXbSDkWmGSDhFTqxdKxinZcyAeSJ33eE1VPiqfwfgcBcmyMRijYEDNb", + "slot": 342369414, + "block_time": 1748177913, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7" + ], + "funded": [ + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" + ] + }, + "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q": { + "address": "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 14 + }, + "first_seen": 1784685663, + "birth_edge": null, + "fee_payers": [ + "44XgYLTyNBWQjQeGwz3xY32CmYV2cFi3b2DzEMqEfTaK" + ], + "funded": [ + "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo" + ] + }, + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm": { + "address": "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", + "owner": null, + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1784580094, + "birth_edge": { + "sink": "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", + "sources": [ + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP" + ], + "value": 50607243678, + "signature": "2h4gvshcwfRhkDXQhiLMAWYTWM4xMUnW8iGdJpEnhQRUYZy5DuMQma319GnL8wCprP7MTXGgb8ymBWvQ1jwTwoht", + "slot": 434168171, + "block_time": 1784580094, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP" + ], + "funded": [ + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ" + ] + }, + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K": { + "address": "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 581 + }, + "first_seen": 1775667915, + "birth_edge": { + "sink": "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K", + "sources": [ + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr" + ], + "value": 12399485120, + "signature": "1hqU1oga3ZT4F3uT5iAm3W4zB3eiH9vd2Rop2Rtyva35zubCfiYZCFgAaSaBCD48a3iTz1aL9zpTnZ4JRq6yZE5", + "slot": 411892813, + "block_time": 1775667915, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr" + ], + "funded": [ + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" + ] + }, + "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4": { + "address": "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", + "owner": null, + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784839352, + "birth_edge": { + "sink": "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", + "sources": [ + "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB" + ], + "value": 2277591434, + "signature": "3WBxufbiy1k7yxTPpfzqzXPMiLy1oDBHP12itMUY56ggt3fo7ay49N1m5UZNkLqHPgySwd4NaeTyhuexF37YN1jF", + "slot": 434785697, + "block_time": 1784839352, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB" + ], + "funded": [ + "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C" + ] + }, + "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38": { + "address": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 567 + }, + "first_seen": 1759849779, + "birth_edge": { + "sink": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "sources": [ + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + ], + "value": 1000000000, + "signature": "55aNfhpFmWyptpJ52kvWWSpaHePRjZLDNbf9kzwSKgUtvvcp4XFhGpKXbU5tVD4wYFh3DrtjJpfnKfLoyGKkadYN", + "slot": 371813843, + "block_time": 1759849779, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + ], + "funded": [] + }, + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6": { + "address": "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1783601529, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" + ] + }, + "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo": { + "address": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3597 + }, + "first_seen": 1736334578, + "birth_edge": { + "sink": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "sources": [ + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH" + ], + "value": 170000000000, + "signature": "2MLS8L4uUc8L3nNZpej5csKZ3d5JuNqKGkUjpX1E1ebmqEBzcxBP3ZccC4FvbAgAFWchKPoey8mhH98JCnCxCQ9x", + "slot": 312653033, + "block_time": 1736334578, + "ambiguous_attribution": false + }, + "fee_payers": [ + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH" + ], + "funded": [] + }, + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw": { + "address": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 155 + }, + "first_seen": 1780608496, + "birth_edge": { + "sink": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 3965000, + "signature": "2jPiYFrpAtgu9YXus8V7NPkAWW6xWy5zBdh42P7S5sticdDDVgPGo91ne8yDeLjXhZHyH6zxZnWg69yPXRApwheC", + "slot": 424324895, + "block_time": 1780608496, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ": { + "address": "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 9767 + "Exact": 2998 }, - "first_seen": 1781265894, + "first_seen": 1780098381, "birth_edge": { - "sink": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "sink": "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw" ], - "value": 209692102518, - "signature": "2WnT866FtZdcXqRvfQoypXGhWSQRtqa4cCg453yjkGSuZA9fs8LJviugPbbGP9GYu3TDJhohtJeDwq1AuoeqJBFi", - "slot": 425977688, - "block_time": 1781265894, - "ambiguous_attribution": true + "value": 800000000, + "signature": "3owWqGQL5cNVZcCTN235bbaS2gyez3LxGz663Gej6dtmqBhUyrbhJNfoP1VaVryXN47bdKWoxji34CAu6oUPsxdP", + "slot": 423040927, + "block_time": 1780098381, + "ambiguous_attribution": false }, "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw" + ], + "funded": [] + }, + "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P": { + "address": "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 96 + }, + "first_seen": 1784854429, + "birth_edge": { + "sink": "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "sources": [ + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT" + ], + "value": 6000000000, + "signature": "5KA9mkbPhkRXKEhf1FeiRuxHphiMMkRgq3FsNjkKk2Ra7gRMMqmB7V5gEvocEESMeK24LVsUC24gWjBx7EyiX3Kp", + "slot": 434821643, + "block_time": 1784854429, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT" ], "funded": [] }, - "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s": { - "address": "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s", + "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis": { + "address": "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD": { + "address": "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 20000 + "Exact": 627 + }, + "first_seen": 1753997946, + "birth_edge": { + "sink": "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "sources": [ + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" + ], + "value": 123066991, + "signature": "5Rtx3JoWd3XaAGxkckRh6w7aeW4W9M2ZbG8NQt7SDLdunYiy5eHXiF3Kc9Dp8QBMJABzH2T2ctoAzCEZF57ckAAB", + "slot": 357026958, + "block_time": 1753997946, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" + ], + "funded": [] + }, + "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA": { + "address": "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 }, - "first_seen": 1780918462, + "first_seen": null, "birth_edge": null, "fee_payers": [], + "funded": [] + }, + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr": { + "address": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784599658, + "birth_edge": { + "sink": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "sources": [ + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + ], + "value": 440000000, + "signature": "4QJkfNCAWjNh2mfYrx9WN6EBe6cCpPUABueLgf4WfqJ5smBUdqbHDcsQXAHT6d8a6q4hFNMvf7jAozFE4jw1rXwj", + "slot": 434214781, + "block_time": 1784599658, + "ambiguous_attribution": false + }, + "fee_payers": [ + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + ], + "funded": [] + }, + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob": { + "address": "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 108 + }, + "first_seen": 1784218241, + "birth_edge": null, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], "funded": [ - "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h" + "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG" ] }, - "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9": { - "address": "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9", + "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh": { + "address": "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 5445 + "Exact": 2 }, - "first_seen": 1718412672, + "first_seen": 1784649420, "birth_edge": { - "sink": "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9", + "sink": "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", "sources": [ - "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd" + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr" ], - "value": 86090000000, - "signature": "53RCfHfFHr7YhKq4wdNzoFBeMfwW1LFk7D2MRY2sXmkHKEdW1fqXFe5UHu9bkwnjxHFqmWC9rsQpF4quxzgrmirL", - "slot": 271894160, - "block_time": 1718412672, + "value": 1850143000, + "signature": "3YgFygisXK1X8kyUXV6LzPEcJ36tKUDkuyo7UvX3KdwULNGDe2wLYiB7KSCVcyM9NejVf2Q4Gr2KV2p11SSvGbCy", + "slot": 434333385, + "block_time": 1784649420, "ambiguous_attribution": false }, "fee_payers": [ - "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd" + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr" ], "funded": [] }, - "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR": { - "address": "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR", + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb": { + "address": "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784389224, + "birth_edge": { + "sink": "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 807213726, + "signature": "2MwPJoYeQHsFvpTXyue8m776mx5fBtFDYgY7hcZpTuz641UR5R8xz7yoe7NXT8e1mRojMFpwKs6Pkdk1fFgSe6zr", + "slot": 433716518, + "block_time": 1784389224, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ" + ] + }, + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT": { + "address": "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 8 + "Exact": 87 }, - "first_seen": 1784677471, + "first_seen": 1784838909, "birth_edge": { - "sink": "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR", + "sink": "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT", "sources": [ - "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB" + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1" ], "value": 6000000000, - "signature": "BgHQ66wJZyJ8Ku1X6nLcW3wTjWrz2tu3kXacjCxvFPmZqMyvZUPA6enxobD3mh4V7vF5AeyP3MCEzmP1Fp3aejo", - "slot": 434400144, - "block_time": 1784677471, + "signature": "3wsTxnCcjxate2zoeFse3mstAwC5KUkvVH4AWjgi9FTKZmwvm7vjJb7N5EPGR5rpcDePeFVXxopoMKiWRsG67bTu", + "slot": 434784661, + "block_time": 1784838909, "ambiguous_attribution": false }, "fee_payers": [ - "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB" + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1" ], "funded": [ - "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb" + "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P" ] }, - "5otWRyzcb7D7DEGZoRJrn2RQ8mbGDDpWCntjh45fFKH1": { - "address": "5otWRyzcb7D7DEGZoRJrn2RQ8mbGDDpWCntjh45fFKH1", - "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB": { + "address": "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB", + "owner": null, "executable": false, "signatures": { - "Exact": 0 + "Exact": 44 }, - "first_seen": null, - "birth_edge": null, - "fee_payers": [], + "first_seen": 1768894237, + "birth_edge": { + "sink": "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB", + "sources": [ + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx" + ], + "value": 849079919, + "signature": "ZPtjQoLcno8LwEZaoxwJrAZfMfcHmszyxAA1V2FW6ohJxa3tbczaP1T4s4d5HgxHTxDS7tTjRK5GnV3x55nWPCQ", + "slot": 394707808, + "block_time": 1768894237, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx" + ], + "funded": [ + "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG" + ] + }, + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN": { + "address": "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784377763, + "birth_edge": { + "sink": "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1299793806, + "signature": "wbcYZ4GZzni8ThU986zLwqQUmhcY4gZfGFBbC1ffpv6TwUkWtxPLvUt6FB3fT9DvuaEKu21bsm6RwpSBVgrkiEh", + "slot": 433689096, + "block_time": 1784377763, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9" + ] + }, + "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU": { + "address": "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784657908, + "birth_edge": { + "sink": "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1996965000, + "signature": "5bJGTURSxfgDoe8QBCjFXtRwNcrBHqLyN8oJ3HjztNDMnbgtzNG11DtZ51MgkqAkcii2BWghA9KB3MitvWTQxF5d", + "slot": 434353532, + "block_time": 1784657908, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94": { + "address": "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894203, + "birth_edge": { + "sink": "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94", + "sources": [ + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf" + ], + "value": 849109919, + "signature": "2XXhJExg7yyA6WH34H5Q99ZtCVavER65hC3V7tf6oNm5onx4KDVoDEpSgAaoupvn721NrE7KdSiMQ4cjKZY1UfJy", + "slot": 394707725, + "block_time": 1768894203, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf" + ], + "funded": [ + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" + ] + }, + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR": { + "address": "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12754 + }, + "first_seen": 1710763330, + "birth_edge": { + "sink": "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", + "sources": [ + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" + ], + "value": 1200000000, + "signature": "3BsEYvaLSedjLWzUCzbGUVGT33rwY35m6mzWnXSLit5zTTJ7vctb7oDjhv1mCdUZLWtRHcLMYSxYz5Dp4hhhc5Nw", + "slot": 254924622, + "block_time": 1710763330, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" + ], "funded": [ - "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1" + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" ] }, - "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G": { - "address": "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz": { + "address": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12036 + }, + "first_seen": 1781801872, + "birth_edge": { + "sink": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "sources": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ], + "value": 100000000, + "signature": "2B1U3i2nZKDXancwuyt9UxUa7AJcqgiVyuXzZhECVSZwiy5CKYE7xe5KcEgGymmMS2FcifLQRD7PNgDhhE3zGEkJ", + "slot": 427328708, + "block_time": 1781801872, + "ambiguous_attribution": false + }, + "fee_payers": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ], + "funded": [] + }, + "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU": { + "address": "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2660 + }, + "first_seen": 1777272559, + "birth_edge": { + "sink": "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "sources": [ + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V" + ], + "value": 2500000000, + "signature": "ETzNdupbU3xXhkPGEw9Hn8tPN6NDugSpsVob4iHjzcfzccdcUAisMiYbqkwSbCm6AxsfqxZvhjfCCUC3P1kEHRc", + "slot": 415950310, + "block_time": 1777272559, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V" + ], + "funded": [] + }, + "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U": { + "address": "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 1675 + "Exact": 272 }, - "first_seen": 1781912647, + "first_seen": 1757688961, "birth_edge": { - "sink": "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", + "sink": "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", "sources": [ "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 8341210350, - "signature": "66yx9Y9Kx2kDn414U79FzGUmHKqC6yjs2pwHYGVyKU7Hz78xoERgzk3aRLu6ER2Lsf8WSoYgKV5XmRBUoBjN1uCi", - "slot": 427608057, - "block_time": 1781912647, + "value": 93700000, + "signature": "3PeWiircHtHEH9T78i4FKAoUNP3568WEdnJr8PgTPdhPPi958X6NrtthHtGn2r21ovMKKWwCo8TkMtc6rmzw6odX", + "slot": 366350032, + "block_time": 1757688961, "ambiguous_attribution": true }, "fee_payers": [ @@ -616,967 +3571,989 @@ ], "funded": [] }, - "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo": { - "address": "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo", - "owner": "11111111111111111111111111111111", + "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR": { + "address": "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1768133169, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi": { + "address": "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18105 + }, + "first_seen": 1738500891, + "birth_edge": { + "sink": "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", + "sources": [ + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + ], + "value": 29400000000, + "signature": "4ChkDoLctkN81tVBKsZMZVqvdAZViy4zrsKb9K8FiiuD4zvuJnzXq2qjMZAnVNqReUqo7Tb12d3knN7qTDW96mZw", + "slot": 317992649, + "block_time": 1738500891, + "ambiguous_attribution": false + }, + "fee_payers": [ + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + ], + "funded": [ + "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1" + ] + }, + "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX": { + "address": "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff": { + "address": "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1784042134, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x": { + "address": "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x", + "owner": null, "executable": false, "signatures": { - "Exact": 1455 + "Exact": 203 }, - "first_seen": 1777633794, + "first_seen": 1784934326, "birth_edge": { - "sink": "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo", + "sink": "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x", "sources": [ - "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L" + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" ], - "value": 258994628, - "signature": "5xTJvzkJ4KLtr61BQu17Zw5s48piQVRNqGfYW6uEF9Chz3pbYKKWj9SgEXgZqXZzwoWTWxfQwx1pk7pfmVk8xpea", - "slot": 416863953, - "block_time": 1777633794, + "value": 54374849153, + "signature": "4Y7SpKFVWPXmK1GJQMsZfuQnxiYyoi7NcJbchUzRemXMGt3DWRUWJBT7L8seY3MymYCvHhSv7XravPdS34KHgmpL", + "slot": 435012112, + "block_time": 1784934326, "ambiguous_attribution": false }, "fee_payers": [ - "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L" + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" ], - "funded": [] + "funded": [ + "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za" + ] }, - "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF": { - "address": "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF", + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn": { + "address": "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 20000 + "AtLeast": 60000 }, - "first_seen": 1784873051, + "first_seen": 1783491639, "birth_edge": null, "fee_payers": [], "funded": [ - "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM" + "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9" ] }, - "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs": { - "address": "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", - "owner": "11111111111111111111111111111111", + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K": { + "address": "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", + "owner": null, "executable": false, "signatures": { - "Exact": 1798 + "Exact": 55 }, - "first_seen": 1782249772, + "first_seen": 1730861242, "birth_edge": { - "sink": "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", + "sink": "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" ], - "value": 214667850, - "signature": "2uBFoGA742GiVkxExn7xvJwoswYHXze3H14E5jxJiPZVkSntt2nhG4awCL2jShRr7Dap88nokh7rahWEWf9N8BPx", - "slot": 428455127, - "block_time": 1782249772, - "ambiguous_attribution": true + "value": 50000000, + "signature": "BoLFuKgcg38xZSUt14Y5926pkU6LLbaXAKPHPqTzfCKUCYyRNa5T3msnqj7QtwrjP8FUFHxsTKELrjYKA1gUvtX", + "slot": 299749544, + "block_time": 1730861242, + "ambiguous_attribution": false }, "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" ], - "funded": [] + "funded": [ + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" + ] }, - "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug": { - "address": "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug", + "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq": { + "address": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 164 + "Exact": 294 }, - "first_seen": 1730137646, + "first_seen": 1764868637, "birth_edge": { - "sink": "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug", + "sink": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", "sources": [ - "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem" ], - "value": 274487000, - "signature": "2DWsZNYcSuEfFqWocAACHHGpMFjNJjSFM6Af7tCd7qK7MvUVrT5zS3RTrMbQVS8DiNFyFZxn4d1KHbfJmXHH6Re3", - "slot": 298217842, - "block_time": 1730137646, + "value": 1390000000, + "signature": "42jLxFrfSKoLo8ZPnaMZfGU56WVo4mrSi1RUEweRK84j2R4vSSRetdHu9HErxgK6eR87R6vt2c7T2C4KUTaDjf4F", + "slot": 384466286, + "block_time": 1764868637, "ambiguous_attribution": false }, "fee_payers": [ - "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem" ], - "funded": [ - "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6" - ] + "funded": [] }, - "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ": { - "address": "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", + "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do": { + "address": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 2540 + "Exact": 203 }, - "first_seen": 1774048122, + "first_seen": 1776858197, "birth_edge": { - "sink": "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", + "sink": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" ], - "value": 2684550000, - "signature": "5JGkx4gLFdFnb8RyE1i2vHX8YhnXpf3jKopxGQJLt2W71QTbq9V8C5WWB1uUVgjVDemFL2Cr9hg7E7ovMD6br4SC", - "slot": 407765285, - "block_time": 1774048122, - "ambiguous_attribution": true + "value": 1007615782, + "signature": "2KPM9xqT2mfMAqEZDX51t6o3RgYoLnJZgU6NPzZ57YV14DZy8VsnyYxntqnKbhtYAZwE4puvnjUP9PmE5y4aUBa1", + "slot": 414903289, + "block_time": 1776858197, + "ambiguous_attribution": false }, "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" ], "funded": [] }, - "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7": { - "address": "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7", + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW": { + "address": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 12414 + "Exact": 521 }, - "first_seen": 1773423037, + "first_seen": 1778972678, "birth_edge": { - "sink": "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7", + "sink": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", "sources": [ - "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 74999986000, - "signature": "5z7rRaVncRKAr8uKafQww17wURn5r1Nrso6rrjX33uWt4RcNDhZwJ3WnUzjQLrhPLvvarQUHiyF43yATFSwmngpR", - "slot": 406181631, - "block_time": 1773423037, - "ambiguous_attribution": false + "value": 6632702930, + "signature": "vJcCHLLH6FaWPDarJWTvMdpKckbK9yPGhy9qgUgypRyheTmVdPiEMf5vCioqKT82iF8G7VZ95vXGuYWoECTAh2B", + "slot": 420215149, + "block_time": 1778972678, + "ambiguous_attribution": true }, "fee_payers": [ - "9WL2A89YBr6X47ABKYNzPentWiBA3H8tpaiuf5CaYHx6" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], - "funded": [] + "funded": [ + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr" + ] }, - "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4": { - "address": "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4", + "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH": { + "address": "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 75 + "Exact": 368 }, - "first_seen": 1777418100, + "first_seen": 1745361264, "birth_edge": { - "sink": "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4", + "sink": "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", "sources": [ - "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8" + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn" ], - "value": 1535976910, - "signature": "4QqKxxNWo8aMdnVwxt2HAZhr971T9NoiEoUrLLpao8iE6UYJP5P5v6Phpr6Dhmvr5CL8eC2gScqDPjNnRHdvxeig", - "slot": 416317031, - "block_time": 1777418100, + "value": 1000000000, + "signature": "5Ugmig4PTLRcf3GTuiY6G47gQn5bxj3UDp4YpKDsvY1JaPwwn1DMU1bQPnTXgrP8wQtrEZfmpLthv8gjADrp7mpu", + "slot": 335243820, + "block_time": 1745361264, "ambiguous_attribution": false }, "fee_payers": [ - "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8" + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn" ], "funded": [] }, - "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2": { - "address": "7aGvYPiRiJL2gp6hiNkm1orjzZwwoa1PfCGZ8UMCgzY2", - "owner": "11111111111111111111111111111111", + "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o": { + "address": "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", "executable": false, "signatures": { - "Exact": 846 + "Exact": 0 }, - "first_seen": 1737286757, + "first_seen": null, "birth_edge": null, - "fee_payers": [ - "87XFc4oRJNVCihAhexTcDhr9CqZJcD5WafPecRJxY7Fh" - ], + "fee_payers": [], "funded": [ - "3pmWhU2m9g5t7gY6sm5mMfGe5fo6nZJ2rbGc1cXZWbDL" + "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu" ] }, - "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me": { - "address": "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me", + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn": { + "address": "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 166 + "Exact": 5289 }, - "first_seen": 1784728932, + "first_seen": 1702336727, "birth_edge": { - "sink": "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me", + "sink": "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn", "sources": [ - "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb" + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" ], - "value": 6000000000, - "signature": "5egDP47NwEtjW6z3FGPu9C8TN6vuKzc5DWdW85GfBcPozErf2Hwz7SAT8uMRLqseRW1zJf9zyZDKzsRS7nU6NuE7", - "slot": 434522672, - "block_time": 1784728932, + "value": 4372981634, + "signature": "5KeWCHfUdwtMws4e48ZDAqSa2K6bFWeiX4irurLzrFw1geWqtgjHvY1F8T8Cma4n6XuaJ3TbQBaKFZjeLGEK6Wau", + "slot": 235369057, + "block_time": 1702336727, "ambiguous_attribution": false }, "fee_payers": [ - "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb" + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" ], - "funded": [] + "funded": [ + "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH" + ] + }, + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM": { + "address": "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 157 + }, + "first_seen": 1780522066, + "birth_edge": null, + "fee_payers": [ + "7rtiKSUDLBm59b1SBmD9oajcP8xE64vAGSMbAN5CXy1q" + ], + "funded": [ + "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW" + ] }, - "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L": { - "address": "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L", + "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx": { + "address": "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 749 + "Exact": 1150 }, - "first_seen": 1769731281, + "first_seen": 1737448881, "birth_edge": { - "sink": "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L", + "sink": "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", "sources": [ - "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM" + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B" ], - "value": 1750000000, - "signature": "5QzbCSjB8yFUJJxW4VA6N3FMboK5PzFjLzmg17FA9D37Fwaob7mSBrihErDHAHqgBnHPVUfo5hLhaECpeXDKnRXv", - "slot": 396800592, - "block_time": 1769731281, + "value": 672745485, + "signature": "64C1Msn6wiLA7U5x5ZjYhPx2cj813py9Pft9Bc41VYtbtWHinBJefXt3rj5KaWTuvXC7aoYwr3Y8tsfj1zed1ZSw", + "slot": 315391758, + "block_time": 1737448881, "ambiguous_attribution": false }, "fee_payers": [ - "3F7LpWtX1vnPTMPrtRsj5kf888WHQxEYrEEfNx1i9TNM" + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B" ], - "funded": [ - "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u" - ] + "funded": [] }, - "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z": { - "address": "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z", + "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ": { + "address": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 2 + "Exact": 58 }, - "first_seen": 1784889281, + "first_seen": 1783645887, "birth_edge": { - "sink": "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z", + "sink": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", "sources": [ - "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 357844785, - "signature": "3x7KfTZsQ3qacJ9YQ2GAkVNy5DH5fywQDuNDWHUPTSRdkZx2WXstn6r2aUuYNtexLskp9j3krmGMserB1uza47Xy", - "slot": 434904811, - "block_time": 1784889281, - "ambiguous_attribution": false + "value": 14433952357, + "signature": "4RbXiaGAmS7WfUmruWn3zBUTwaF2WzVDno2bSTiZFH7cbYSeVeUGgS184pvQaV3ySoRBNGkx6r68M8r5AA5ZbJTo", + "slot": 431912014, + "block_time": 1783645887, + "ambiguous_attribution": true }, "fee_payers": [ - "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "funded": [] }, - "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E": { - "address": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", - "owner": "11111111111111111111111111111111", + "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp": { + "address": "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp", + "owner": null, "executable": false, "signatures": { - "AtLeast": 20000 + "Exact": 0 }, - "first_seen": 1675029970, + "first_seen": null, "birth_edge": null, "fee_payers": [], "funded": [] }, - "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1": { - "address": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL": { + "address": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 2154 + "Exact": 89 }, - "first_seen": 1684141322, + "first_seen": 1784429559, "birth_edge": { - "sink": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "sink": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", "sources": [ - "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 90000000, - "signature": "52ZPH7cfAzwauMYh529GgHWhTzRwReNdJyWYr5ZugsAB5gvSmP6CYuCV1U79B9SLxLjk6qy5nsDUmao8Xa5y5EBB", - "slot": 194024003, - "block_time": 1684141322, - "ambiguous_attribution": false + "value": 1987000000, + "signature": "22tiKy2Uhr5HbzaoUTkf5gTS2VHov1xMY998kBCQbYybACx6Y3fJtxRBaj8ppaMyahAGEgnC5SggnKt6HbxcwNRb", + "slot": 433813000, + "block_time": 1784429559, + "ambiguous_attribution": true }, "fee_payers": [ - "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], - "funded": [ - "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6" - ] + "funded": [] }, - "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS": { - "address": "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS", - "owner": null, + "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B": { + "address": "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 4 + "Exact": 145 }, - "first_seen": 1784763254, + "first_seen": 1769758558, "birth_edge": { - "sink": "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS", + "sink": "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", "sources": [ - "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 123619297270, - "signature": "2cECE1YoJRoHQy2qRWbyonojR7N6G7cAwCFK8RMz7suMaM1AczRPVJr9JNpjWZH6evSV42UcKpeetDTHEueNmXzG", - "slot": 434604242, - "block_time": 1784763254, - "ambiguous_attribution": false + "value": 193300000, + "signature": "52ygQjkxX6CiTWcaT8KoncyGrbR97uAqzrJ91LySCtkZDCNDa3WTHpnvNSJkRXacynWy4D5j16sFCh1PWGdesPET", + "slot": 396868730, + "block_time": 1769758558, + "ambiguous_attribution": true }, "fee_payers": [ - "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], - "funded": [ - "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2" - ] + "funded": [] }, - "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP": { - "address": "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP", + "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt": { + "address": "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 4706 + "Exact": 11 }, - "first_seen": 1739322324, + "first_seen": 1784509948, "birth_edge": { - "sink": "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP", + "sink": "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", "sources": [ - "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362" + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe" ], - "value": 10000000000, - "signature": "7qWiwuckPXYWmebpR7dcDePFe2ck5fb9P4Vtcnia6q79VyEUvGGyioUbhEEfbb92v6afMhZZKQc9SLfjStygNLK", - "slot": 320058361, - "block_time": 1739322324, + "value": 14000000000, + "signature": "55y1RvP9p644eWZy6JypDF39fB4u7XCBXYmQhjDRX5AgytWCpsC2J4SiffzCo9gLPHpPoqs6AXrCXyb7mgUooZXD", + "slot": 434004156, + "block_time": 1784509948, "ambiguous_attribution": false }, "fee_payers": [ - "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362" + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe" ], "funded": [] }, - "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2": { - "address": "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2", + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm": { + "address": "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm", "owner": null, "executable": false, "signatures": { - "Exact": 32 + "Exact": 2 }, - "first_seen": 1784763527, + "first_seen": 1768894210, "birth_edge": { - "sink": "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2", + "sink": "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm", "sources": [ - "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS" + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94" ], - "value": 100000000000, - "signature": "wdtUyL83Tjg7qH1qdd8Tb13cf56tkME445ATpk5MsfdvVqnNrEPgdiShLcTgoErfnxSnBxRXkc2HNX1BC4vPbDb", - "slot": 434604898, - "block_time": 1784763527, + "value": 849104919, + "signature": "2ga76DPJVD9CWDYmtpb17BURi9EcLNFxeNFi172x5aRN9es1qAh5HRHf9ZNCzGtADfp9LzzcF2t26MdDn5jEDT5C", + "slot": 394707744, + "block_time": 1768894210, "ambiguous_attribution": false }, "fee_payers": [ - "9L6aT5vj92HvC1M2U63RpihHeJqdJn4ZkqDCwSUJVSWS" + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94" ], "funded": [ - "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB" - ] - }, - "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2": { - "address": "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 20000 - }, - "first_seen": 1783468695, - "birth_edge": null, - "fee_payers": [], - "funded": [ - "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug" - ] - }, - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW": { - "address": "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 20000 - }, - "first_seen": 1782630865, - "birth_edge": null, - "fee_payers": [], - "funded": [ - "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", - "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi", - "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", - "1xxAAZZjmZXg4LyhZus3D2GyWD5CQxi7UH5RMm8gqix", - "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh", - "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", - "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", - "6UCU24ZM5tYrj3vgobuc2TH4Fgf45Pdo4R5YV1cTwKbs", - "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", - "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", - "65hBBDbKSmcpLtKcWHkdamVscqPMaTwPkhQ2SieZwo5G", - "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG", - "6cTWeEoKH3ZXLtaopZ2w8f3tAYHa1aZ955F7Be3v2JjJ", - "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", - "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" - ] - }, - "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ": { - "address": "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 20000 - }, - "first_seen": 1784656384, - "birth_edge": null, - "fee_payers": [], - "funded": [ - "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh" ] }, - "AxG818LtyEa88dJDoHeYHW2WyNXrCLYUzr6ifL5qn4KB": { - "address": "AxG818LtyEa88dJDoHeYHW2WyNXrCLYUzr6ifL5qn4KB", - "owner": null, - "executable": false, - "signatures": { - "Exact": 0 - }, - "first_seen": null, - "birth_edge": null, - "fee_payers": [], - "funded": [] - }, - "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6": { - "address": "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6", + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor": { + "address": "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 461 + "Exact": 856 }, - "first_seen": 1730991438, + "first_seen": 1783108022, "birth_edge": { - "sink": "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6", + "sink": "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor", "sources": [ - "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug" + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu" ], - "value": 170300000, - "signature": "3yotNo2nUtSpv7ygmKCi3Y6GA2Th4y1M4MZUfNSMFLJLLZgjpaZV8PTgncmDHp6LaTHbPudF6o8MtHPdZRa1YTkq", - "slot": 300020544, - "block_time": 1730991438, + "value": 200000000, + "signature": "3XBCwvm2pUrPEp3PCUKm4NiNy7pGhpxqpy2u2eWnMBs5sJgoYk55DPp77RSEn4m2vL9C61sWiefBAiArFGEaUgFw", + "slot": 430586035, + "block_time": 1783108022, "ambiguous_attribution": false }, "fee_payers": [ - "6WY85YarfLbVcys9pYjCPPbtafy8NywxZ2f2dqwEauug" + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu" ], "funded": [ - "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L" + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe" ] }, - "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb": { - "address": "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb", + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd": { + "address": "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 118 + "Exact": 48 }, - "first_seen": 1784559066, + "first_seen": 1765645486, "birth_edge": { - "sink": "B4PcoH3JyxhbFuq6UUHtdeAJZdPja84imJq3V34xGLnb", + "sink": "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd", "sources": [ - "EH4vhUSeJrk17J5PkG4W2xziF1H8AVwLTH1AXx4ACvqt" + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC" ], - "value": 6000000000, - "signature": "2DpouWNQzV4La7kGGFWEu8xAUrYsxX1iYLv2YjzUV7o52LGRTmoc2yBKdSeTYC2ftWLUaRyxa7dJoVi4d2aRX7md", - "slot": 434118998, - "block_time": 1784559066, + "value": 88200000, + "signature": "sH5bnyJn2BNFN3TgD2isA2G5ygXEsGzWr9kpgN2PW5Ubxk3nAHyLRprFmS5M2FzdbTTZD2RjxfgH7xfbARZtdHc", + "slot": 386459128, + "block_time": 1765645486, "ambiguous_attribution": false }, "fee_payers": [ - "EH4vhUSeJrk17J5PkG4W2xziF1H8AVwLTH1AXx4ACvqt" + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC" ], "funded": [ - "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE" + "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ" ] }, - "B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc": { - "address": "B5eHbv7xrBhjvA2NFYr7hP8LDwJmEVdnfDzVkEKUDXqc", + "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM": { + "address": "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 20000 + "Exact": 12 }, - "first_seen": 1768579501, - "birth_edge": null, - "fee_payers": [], - "funded": [] - }, - "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM": { - "address": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 20000 + "first_seen": 1784461923, + "birth_edge": { + "sink": "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 990500000, + "signature": "9duBpzRRMAZ2cQv2n79HKMzurHUBWmDWdDwNR8y6U48bnG6HKY2HZnrQxXNFVFpg8RtSy3KPNEY4ByMKHEBJCEb", + "slot": 433890247, + "block_time": 1784461923, + "ambiguous_attribution": true }, - "first_seen": 1783225581, - "birth_edge": null, - "fee_payers": [], + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], "funded": [] }, - "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4": { - "address": "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ": { + "address": "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ", "owner": null, "executable": false, "signatures": { - "Exact": 0 + "Exact": 1670 }, - "first_seen": null, - "birth_edge": null, - "fee_payers": [], - "funded": [] + "first_seen": 1784580105, + "birth_edge": { + "sink": "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ", + "sources": [ + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm" + ], + "value": 49209767318, + "signature": "2BVoGzFZrFnuRLqpW1EAHxkFXmnp7xWp1wog4n3EBnCbTgQLvSgFVZHB8z4Z1FgdzjYGBh9tTUFZ1aLF2XtYCuKU", + "slot": 434168198, + "block_time": 1784580105, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm" + ], + "funded": [ + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" + ] }, - "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6": { - "address": "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6", + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN": { + "address": "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 20000 + "Exact": 18203 }, - "first_seen": 1784446498, + "first_seen": 1712861701, "birth_edge": null, - "fee_payers": [], + "fee_payers": [ + "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J" + ], "funded": [ - "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW" + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" ] }, - "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw": { - "address": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7": { + "address": "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 155 + "Exact": 48 }, - "first_seen": 1780608496, + "first_seen": 1784809669, "birth_edge": { - "sink": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "sink": "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", "sources": [ "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 3965000, - "signature": "2jPiYFrpAtgu9YXus8V7NPkAWW6xWy5zBdh42P7S5sticdDDVgPGo91ne8yDeLjXhZHyH6zxZnWg69yPXRApwheC", - "slot": 424324895, - "block_time": 1780608496, + "value": 5973000000, + "signature": "3MSs2ea12TniywAz7iM9anGzcgJr8oB1kCVxVQm8YgQxqoCwwPpzBhg6EACKhpDuWoT6J723YZfrnDq8qqfLSTRJ", + "slot": 434715093, + "block_time": 1784809669, "ambiguous_attribution": true }, "fee_payers": [ "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], - "funded": [] + "funded": [ + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1" + ] }, - "BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7": { - "address": "BwEqAJZcZfP1qi4pu5ud6ArHaQqZZ7o454H4UVhFY2B7", + "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k": { + "address": "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 20000 + "Exact": 944 }, - "first_seen": 1743115811, + "first_seen": 1781405876, "birth_edge": null, - "fee_payers": [], + "fee_payers": [ + "9HGyyJnWK4b67E97bB4SXpjbxpYmTBMaKNmWmfmYe4DK" + ], "funded": [] }, - "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr": { - "address": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", - "owner": "11111111111111111111111111111111", + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B": { + "address": "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B", + "owner": null, "executable": false, "signatures": { - "Exact": 5 + "Exact": 15 }, - "first_seen": 1784599658, + "first_seen": 1737447329, "birth_edge": { - "sink": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "sink": "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B", "sources": [ - "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" ], - "value": 440000000, - "signature": "4QJkfNCAWjNh2mfYrx9WN6EBe6cCpPUABueLgf4WfqJ5smBUdqbHDcsQXAHT6d8a6q4hFNMvf7jAozFE4jw1rXwj", - "slot": 434214781, - "block_time": 1784599658, + "value": 672750485, + "signature": "4gBVShePEtPdc8c9vLekxqxtKnHN1CaY2ECsfrU3Ysoo7AeQGVwwpqLDW9aAWYF3KSsekeoaA1aqHrTqAEHwSGuN", + "slot": 315387958, + "block_time": 1737447329, "ambiguous_attribution": false }, "fee_payers": [ - "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" ], - "funded": [] + "funded": [ + "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx" + ] }, - "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX": { - "address": "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", - "owner": "11111111111111111111111111111111", + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv": { + "address": "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "owner": null, "executable": false, "signatures": { - "Exact": 13 + "Exact": 2 }, - "first_seen": 1779951445, + "first_seen": 1774644756, "birth_edge": { - "sink": "Cp5dcC6LJcZ8KCfbDpXoCurHgds92qDMr49ovRJcHZRX", + "sink": "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", "sources": [ "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 8215125000, - "signature": "3S3LvvA4afcP2UWFcxxtrhRLYeEVx2nk5k516Eh5KFDaL6hv54unSFZsQKkJfwFyopqJoi3FBbspfAsLuEyzEGA4", - "slot": 422670074, - "block_time": 1779951445, + "value": 5973000000, + "signature": "5fTttEt3Pb3F4Sj5dKKrkqFCZEJqR37qyLLYVSA7GwBqbyn4MCv2LDEZBKuh9pGRhdxTJa7xNMKH6c53gXrno7RZ", + "slot": 409284059, + "block_time": 1774644756, "ambiguous_attribution": true }, "fee_payers": [ "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], - "funded": [] - }, - "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz": { - "address": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 11230 - }, - "first_seen": 1781801872, - "birth_edge": { - "sink": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", - "sources": [ - "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" - ], - "value": 100000000, - "signature": "2B1U3i2nZKDXancwuyt9UxUa7AJcqgiVyuXzZhECVSZwiy5CKYE7xe5KcEgGymmMS2FcifLQRD7PNgDhhE3zGEkJ", - "slot": 427328708, - "block_time": 1781801872, - "ambiguous_attribution": false + "funded": [ + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY" + ] + }, + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF": { + "address": "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF", + "owner": null, + "executable": false, + "signatures": { + "Exact": 253 }, + "first_seen": 1730807547, + "birth_edge": null, "fee_payers": [ - "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP" ], - "funded": [] + "funded": [ + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" + ] }, - "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1": { - "address": "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1", + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw": { + "address": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 4 + "Exact": 14354 }, - "first_seen": 1784708703, + "first_seen": 1777091838, "birth_edge": { - "sink": "CtNS8YptrA5Rif1iaemwGozwJBxFYKiyGF2K5a27UNm1", + "sink": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", "sources": [ - "5otWRyzcb7D7DEGZoRJrn2RQ8mbGDDpWCntjh45fFKH1" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 8938355770, - "signature": "2AfWhhdPCHX43rGHUYcLfc9mKuadeZ9BcPwcZdjrdbU1GauBAvUpxeMxCtSwGkZgPftHpeHh4BzdaRMQHBvKkCza", - "slot": 434474544, - "block_time": 1784708703, - "ambiguous_attribution": false + "value": 18571768434, + "signature": "44VV33bxUiT3rDmG3dtN2uxnDx4uBs6keq7PWCmx3rdqWhvPmPXJhrA7Sq7atABrqAkpaBZH5wdRRWMkFQ7j8kNA", + "slot": 415493545, + "block_time": 1777091838, + "ambiguous_attribution": true }, "fee_payers": [ - "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], "funded": [] }, - "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS": { - "address": "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs": { + "address": "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 5 + "Exact": 7899 }, - "first_seen": 1784905824, + "first_seen": 1731918662, "birth_edge": { - "sink": "DdAS1xeVfShiyL9GkZjFjS2az4mtBDqFE5vQoJZnWcxS", + "sink": "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" ], - "value": 1688050000, - "signature": "3okszdffPjqzZwqTbMnoDeW22XyxMzjeg4fH7Sa2zx87HW3sSGwHKbMjzoBbhMiodbFuw4pJREV7yyTMdHRyA6LX", - "slot": 434944185, - "block_time": 1784905824, - "ambiguous_attribution": true + "value": 11000000000, + "signature": "4NrJoknq1Jx9gtAQ8bxTuhWoNxMWggLDQS4XBN1zsNRVXGfCvC21JBkKGsxLH9hppZBa6KxaMLve5iiowk75DVLb", + "slot": 302103526, + "block_time": 1731918662, + "ambiguous_attribution": false }, "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" ], - "funded": [] + "funded": [ + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + ] }, - "DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX": { - "address": "DnpSR1A1gDByxC7X1YWYQDLzZtMxVgLyrH13o9fVVUZX", + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G": { + "address": "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 3 + "AtLeast": 60000 }, - "first_seen": 1784827497, + "first_seen": 1778083481, "birth_edge": null, - "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" - ], - "funded": [] + "fee_payers": [], + "funded": [ + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg" + ] }, - "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb": { - "address": "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb", + "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT": { + "address": "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 50 + "Exact": 4 }, - "first_seen": 1784677540, + "first_seen": 1784758056, "birth_edge": { - "sink": "Dui7P69ZGxj7XDqa4WUWHFSfQQJhJ4WQYfvaDu6epJDb", + "sink": "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", "sources": [ - "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR" + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU" ], - "value": 5972245550, - "signature": "3y1YYF3p7DGGbkcPfP7YZiCUuNVbWu1gkkQUyhbjGrHUWWWyHSK3g84KBRYKxWR5EwLHykacupHUUoPCuvixm9D5", - "slot": 434400306, - "block_time": 1784677540, + "value": 90000000000, + "signature": "2yFwMANAjHWBoCUWXwsF2NS8Vm24oSkE5oJ6RfsDe2jPVeo782fE98nePAJqpuxWL1VkA5iQXoXdDoU8SqopCTR2", + "slot": 434591833, + "block_time": 1784758056, "ambiguous_attribution": false }, "fee_payers": [ - "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR" + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU" ], - "funded": [ - "8GBakVqHS5ZwjGHUVQJNp38LoHBmykhmzz94ygqqS9me" - ] + "funded": [] }, - "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW": { - "address": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq": { + "address": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 509 + "Exact": 65 }, - "first_seen": 1778972678, + "first_seen": 1784659136, "birth_edge": { - "sink": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "sink": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", "sources": [ "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 6632702930, - "signature": "vJcCHLLH6FaWPDarJWTvMdpKckbK9yPGhy9qgUgypRyheTmVdPiEMf5vCioqKT82iF8G7VZ95vXGuYWoECTAh2B", - "slot": 420215149, - "block_time": 1778972678, + "value": 32880718309, + "signature": "4wZbNhexVa96s82hAQbdZ9Pq1fqencGxxmKYCwghVjT6iwxMLyyKBssfwvWVJFXFMXxNwSfrDo8TqZ4AiJbEPC6U", + "slot": 434356458, + "block_time": 1784659136, "ambiguous_attribution": true }, "fee_payers": [ "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], - "funded": [ - "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr" - ] + "funded": [] }, - "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw": { - "address": "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw", + "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB": { + "address": "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB", "owner": null, "executable": false, "signatures": { - "Exact": 263 + "Exact": 2 }, - "first_seen": 1739221109, + "first_seen": 1784839349, "birth_edge": { - "sink": "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw", + "sink": "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB", "sources": [ - "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG" + "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte" ], - "value": 193060356732, - "signature": "fAGMDMYWqckj3yppKkDz6nU16mw6nUyj8eqKz67cuBCk2vyPb41nDZFKVfUokuNdBBHR7DnRD6QVNTC1smJ6xeJ", - "slot": 319802609, - "block_time": 1739221109, + "value": 2277596434, + "signature": "4tkrNcftx61jVaqP4oWYQUrtx9Lf9pMcX8thrArBUUGBgaNV2EfEXAKr717VfdED71dBpdBcDP1TSAZ46uu96grf", + "slot": 434785691, + "block_time": 1784839349, "ambiguous_attribution": false }, "fee_payers": [ - "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG" + "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte" ], "funded": [ - "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362" + "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4" ] }, - "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6": { - "address": "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6", + "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC": { + "address": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 5 + "Exact": 291 }, - "first_seen": 1784723655, + "first_seen": 1783054209, "birth_edge": { - "sink": "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6", + "sink": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", "sources": [ - "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW" + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx" ], - "value": 53379956, - "signature": "5V2TrixS2SaFcCSTh7Z8rQCHwGk8BsHYxbVkiC1LYG7b1kroixqiZTc4xxgExXW799cbRzcxGZzHnNtJJqkfp7PY", - "slot": 434510180, - "block_time": 1784723655, + "value": 180834295, + "signature": "55tLEDwJyuDGaSo8c6yYz4bj5utz7zJiVtQnsQuwqKg7abdYWVoK1vFkLQd7pyJhjh4r8ku6i9oCyXRa8a6kX18z", + "slot": 430452695, + "block_time": 1783054209, "ambiguous_attribution": false }, "fee_payers": [ - "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW" + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx" ], "funded": [] }, - "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi": { - "address": "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi", + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf": { + "address": "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf", "owner": null, "executable": false, "signatures": { - "Exact": 22 + "Exact": 2 }, - "first_seen": 1784545211, + "first_seen": 1768894200, "birth_edge": { - "sink": "EfTXGDatPDADBzrkXF4UDceXfZomfZapkVtWNxRqiadi", + "sink": "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf", "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ" ], - "value": 911592746, - "signature": "3mU1pusTugbjUavbBoZ22NW4vuCtQpkg3cLhD7LzftumBwNyYjjym77QgZDqw3P1MJyPGHmjFvhwfmBdqzkPM22C", - "slot": 434086683, - "block_time": 1784545211, - "ambiguous_attribution": true + "value": 849114919, + "signature": "5Gb3um1roTYvbjCFckDeFVPkU7QxKeShYSL3KWDxSEX8qhCrwgDEJgbSiuL3L4tCXzk1GaguKXBbmAU2yjAVmkdG", + "slot": 394707716, + "block_time": 1768894200, + "ambiguous_attribution": false }, "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ" ], "funded": [ - "8bn7JfKdDRVgPvE5iQvQPAVKyWyVeSr7bGbqC7VURB3z" + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94" ] }, - "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L": { - "address": "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L", - "owner": null, + "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk": { + "address": "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 204 + "Exact": 1151 }, - "first_seen": 1760973563, + "first_seen": 1777988933, "birth_edge": { - "sink": "EhYvkhV8sciC6JtoSLeQ3SPcuuHtSCPdBGV6Zpgjce3L", + "sink": "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", "sources": [ - "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6" + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" ], - "value": 232124163, - "signature": "4myvBjchjS5GpkAefDQzdZy9X3tRiTAqLqXLP15CbfeRozQKLwNkE9cjT1HM1jWLgXWYX1Finn6WPATKvUW7qh1b", - "slot": 374643294, - "block_time": 1760973563, + "value": 2362855000, + "signature": "Vdct3Y5hjgKy2gHHjimC9YE8faisFo7jz3EiFYjmb3yAYuHpY2tE5ZqJcXhMNtbrrXTRr5CxYpCz5djyYzwgJyb", + "slot": 417761237, + "block_time": 1777988933, "ambiguous_attribution": false }, "fee_payers": [ - "B2pLXgxo7pw6zWCHwtcAB8TKTkLn2JscPQ1wE9LbCCp6" + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" ], - "funded": [ - "6K21Da3WJ69MjAQAguwQpMfaPFqJE8fku8USd8yoY6Jo" - ] - }, - "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q": { - "address": "EvGpkcSBfhp5K9SNP48wVtfNXdKYRBiK3kvMkB66kU3Q", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 20000 - }, - "first_seen": 1775141178, - "birth_edge": null, - "fee_payers": [], - "funded": [ - "3UwX8m3zCdepWUCnttRVZDXMzn6u32KK1SqPeBHSqPB3" - ] + "funded": [] }, - "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG": { - "address": "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG", + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho": { + "address": "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho", "owner": null, "executable": false, "signatures": { - "Exact": 65 + "Exact": 600 }, - "first_seen": 1739220909, + "first_seen": 1784462427, "birth_edge": { - "sink": "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG", + "sink": "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho", "sources": [ - "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU" + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva" ], - "value": 193060501712, - "signature": "3XTrWcuTAZ6VPTskSZUcKifL2no86sm4ySA1mKqd1TrtwfmBSH5TGFizJGSUj97L73oWhVHHZxDaZ8QPotXrhKHN", - "slot": 319802102, - "block_time": 1739220909, + "value": 51022180729, + "signature": "4udxkWqxkyLqBAZpUJHNMkZDcFMVTq1BpaC2A5YqNGbm6He6WAYpQGQXMY5J91YcUxxZATqVkJooVP9y3EXqpABd", + "slot": 433891452, + "block_time": 1784462427, "ambiguous_attribution": false }, "fee_payers": [ - "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU" + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva" ], "funded": [ - "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw" + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP" ] }, - "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M": { - "address": "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M", - "owner": "11111111111111111111111111111111", + "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp": { + "address": "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva": { + "address": "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva", + "owner": null, "executable": false, "signatures": { - "Exact": 39 + "Exact": 1629 }, - "first_seen": 1784388028, + "first_seen": 1784212802, "birth_edge": { - "sink": "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M", + "sink": "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva", "sources": [ - "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh" + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE" ], - "value": 713996614, - "signature": "3uB9YzeAF4ZxH7RhAuKzZTEdfY2ZemqdbocxsdNcgxhPTegQLCabEQV4FatVhr3suF7G3WsgTmZi1rM4Rv7bHwDY", - "slot": 433713669, - "block_time": 1784388028, + "value": 22180583705, + "signature": "59BrgexB78Kj9fCJkHmKs2UZYsp5Ntc5ia4RpE3T1WS8xtwyJEhn49w5VcofBvhkHWa9AxfEQsR1L6qjNFKdZnhj", + "slot": 433291830, + "block_time": 1784212802, "ambiguous_attribution": false }, "fee_payers": [ - "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh" + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE" ], - "funded": [] + "funded": [ + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho" + ] }, - "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5": { - "address": "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS": { + "address": "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 20000 + "AtLeast": 60000 }, - "first_seen": 1758757924, + "first_seen": 1784328875, "birth_edge": null, "fee_payers": [], "funded": [ - "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU" + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn" ] }, - "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz": { - "address": "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ": { + "address": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 13 + "Exact": 18873 }, - "first_seen": 1784474206, + "first_seen": 1780029667, "birth_edge": { - "sink": "FX1veGSHZV1U88iDn4Tj9Jqsfk4Qru8wyWpJ6piR3GFz", + "sink": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", "sources": [ "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" ], - "value": 4000000000, - "signature": "2HhrwGqohRREkYJwQikQSNMrdj84hNCE8rEm36vnehrxBDGhdcviJ8r2UaYrjVrkCdoc9C7yZgf1Ry5qqJkySMWe", - "slot": 433919635, - "block_time": 1784474206, + "value": 2442413156, + "signature": "2UXxvAd3GMF7t1ZMWhrXJcFP3q6iYYcZBEMeynG6PWWz8vrttyjdwXo1KSxrYhGU8zchaaM6979gnU1mz4d3dHKr", + "slot": 422867536, + "block_time": 1780029667, "ambiguous_attribution": true }, "fee_payers": [ @@ -1584,402 +4561,354 @@ ], "funded": [] }, - "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN": { - "address": "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN", + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe": { + "address": "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 18203 + "Exact": 712 + }, + "first_seen": 1784227892, + "birth_edge": { + "sink": "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe", + "sources": [ + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor" + ], + "value": 130000000, + "signature": "kTyjASGv9R4C1zjLJriyX96k2ZWgbAwPw3Qd2z3pCzb5cRPuZs3vt8uqzKH2bjr5AdqMDwf8SnxCqbwyt2cWwdm", + "slot": 433328336, + "block_time": 1784227892, + "ambiguous_attribution": false }, - "first_seen": 1712861701, - "birth_edge": null, "fee_payers": [ - "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J" + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor" ], "funded": [ - "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt" ] }, - "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw": { - "address": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq": { + "address": "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 14348 + "Exact": 14 }, - "first_seen": 1777091838, + "first_seen": 1784552253, "birth_edge": { - "sink": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "sink": "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW" ], - "value": 18571768434, - "signature": "44VV33bxUiT3rDmG3dtN2uxnDx4uBs6keq7PWCmx3rdqWhvPmPXJhrA7Sq7atABrqAkpaBZH5wdRRWMkFQ7j8kNA", - "slot": 415493545, - "block_time": 1777091838, - "ambiguous_attribution": true + "value": 23698818508, + "signature": "3uywYCF5VuSzVQkMX6Pghx7SXTsJuW55Jv37zcHt1foh71Nuq6vGrb4keCz7JNFjLMRWkK5Aq2jNmWhqkWbXuH8y", + "slot": 434103135, + "block_time": 1784552253, + "ambiguous_attribution": false }, "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW" ], "funded": [] }, - "G2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3t": { - "address": "G2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3t", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 20000 - }, - "first_seen": 1776054108, - "birth_edge": null, - "fee_payers": [], - "funded": [ - "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd" - ] - }, - "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB": { - "address": "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB", + "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC": { + "address": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 70 + "Exact": 4402 }, - "first_seen": 1784642108, + "first_seen": 1776709519, "birth_edge": { - "sink": "HG9AMzxAyU57FcKSfGxoiZLEaj25Lp4os3h2C1J1pthB", + "sink": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", "sources": [ - "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE" + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" ], - "value": 6000000000, - "signature": "36emCx68q3daUpGUon62Ap25HKquJc4ShAsXFf4WTrQSv3LLkMyzrgHppntKE5x7nhXgc4yHAbiV9emJWZTyWUBt", - "slot": 434315972, - "block_time": 1784642108, + "value": 4909585890, + "signature": "5VEpmW6pcMbLtJQHaJbctF8BUkixM7cH6gwV5yMHLtJtq5DaCpFaKRydWVjQ85d3uA9ErzyjYTkqyfHriUeN1D8E", + "slot": 414528392, + "block_time": 1776709519, "ambiguous_attribution": false }, "fee_payers": [ - "3G7g4bbapfEggBJ2zYVCAMfdEGCHeFWmV6Q1R2Y6iYCE" + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" ], - "funded": [ - "5nfmtYyBWfqgPCa7Q268Z5KvxNN7suoBg335vYJ1f9xR" - ] + "funded": [] }, - "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG": { - "address": "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG", - "owner": null, + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165": { + "address": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 2 + "Exact": 34631 }, - "first_seen": 1784388623, + "first_seen": 1773695393, "birth_edge": { - "sink": "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG", + "sink": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" ], - "value": 1417613532, - "signature": "36LP8gnyhWKzZ7tRs8AqYiryPu1uXRnxkJkCL5UYY3gDLP9HDKxoM7F78gtGZhL4484ggeipkx7br8xuVkjqiCmP", - "slot": 433715069, - "block_time": 1784388623, - "ambiguous_attribution": true + "value": 985566110, + "signature": "4bmfxXv69FjWNEcanuh66h7megBdqernEszUDCEWAf8aND6HTdhfQwo1avroBocetPWkg7iybW8kbGo3aJVK5sux", + "slot": 406873448, + "block_time": 1773695393, + "ambiguous_attribution": false }, "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" ], - "funded": [ - "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw" - ] - }, - "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165": { - "address": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 20000 - }, - "first_seen": 1776692095, - "birth_edge": null, - "fee_payers": [], "funded": [] }, - "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16": { - "address": "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 20000 - }, - "first_seen": 1781821516, - "birth_edge": null, - "fee_payers": [], - "funded": [ - "75ccPySyKNxYxeAW4EHmu2qhkeGR9e8EWocme3mSRMn7" - ] - }, - "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU": { - "address": "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU", + "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf": { + "address": "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf", "owner": null, "executable": false, "signatures": { - "Exact": 88 + "Exact": 2 }, - "first_seen": 1739158938, + "first_seen": 1784834868, "birth_edge": { - "sink": "HY5YfhKFC8yv14VUWAxdjpDP84cgwPqHuFFPmP85AJUU", + "sink": "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf", "sources": [ - "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5" + "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM" ], - "value": 175000000000, - "signature": "4oThEUiwo2rQL8uC6ftsU8CM4T777952pow6Xd1V1g38wLEXcD7Unwfoam7V3h3RscKvknzQYeniAxhqP788yJey", - "slot": 319645868, - "block_time": 1739158938, + "value": 2277611434, + "signature": "4aML9ertmZGFiMCfYQ1UKr1RVxg1u7v8CgRBAwG96wwghohzUqDkNJ6Bhfftmjonh6qAqrB9UNUsb6Lw4CsNC6ya", + "slot": 434775071, + "block_time": 1784834868, "ambiguous_attribution": false }, "fee_payers": [ - "FWznbcNXWQuHTawe9RxvQ2LdCENssh12dsznf4RiouN5" + "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM" ], "funded": [ - "FLpcQHCyZHTt5RE9e2CwdC9KrjsAhjn2MJUm1sbiAhoG" + "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an" ] }, - "HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq": { - "address": "HhWpXSYWqpqL5SndpynYX6bhyCPakVgSEdBYKHeVB7qq", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 14 - }, - "first_seen": 1784431964, - "birth_edge": null, - "fee_payers": [ - "5b89H4qf4gyBvEmdTbVCoEUxvBTMuqtjwpRnVusBwPF7" - ], - "funded": [] - }, - "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u": { - "address": "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u", + "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9": { + "address": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 539 + "Exact": 41 }, - "first_seen": 1780432182, + "first_seen": 1784377766, "birth_edge": { - "sink": "HnPk9tMejhS4atsn9WWXjphkdQDqEfkgh782r1N31G8u", + "sink": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", "sources": [ - "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L" + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN" ], - "value": 89094594, - "signature": "3AAmWu8c1yZAnghr6GUa2VZVep5GxNyWya4YaXB6nZvYXh3Au5z2tZGp8K4dDVV9gHzHoCVPkww6bCeRQkWnq9Am", - "slot": 423882128, - "block_time": 1780432182, + "value": 1299788806, + "signature": "DonAnKr8whXoDnWemm9bqSjoUQdEohd9sus4nhstSat4cF5HxAmnrHQqMEbbNW3s9KhRDYLvL28ifTpBj16tWha", + "slot": 433689104, + "block_time": 1784377766, "ambiguous_attribution": false }, "fee_payers": [ - "8QsoN6vuzSC68BmpnYc7a1bnEBKTg6LGyKjsnRbFjy6L" + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN" ], "funded": [] }, - "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362": { - "address": "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362", - "owner": null, + "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k": { + "address": "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 489 + "Exact": 109 }, - "first_seen": 1739221320, + "first_seen": 1783337142, "birth_edge": { - "sink": "Hqu3WpZ3NFi5JUkuPQ9KvDPNenApHSKfRu6Xxgced362", + "sink": "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", "sources": [ - "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw" + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w" ], - "value": 193060212752, - "signature": "4ueh33xU53mTLFqenjNiZHvF9gZzqG5w7vbFG2Q3j45hwf2hgJWPdByjYckeqYBLc4Z2daRQ2z6svu5ia86Dm9gH", - "slot": 319803144, - "block_time": 1739221320, + "value": 2000000000, + "signature": "3TYxeVhY5FHcpE3BqdMtf6RUScLkjcsXBpZnPjc8YfGiTXw25vLWWvd3vZWPE9V6kBcLpzMrph82uRoprzHPNvKU", + "slot": 431153926, + "block_time": 1783337142, "ambiguous_attribution": false }, "fee_payers": [ - "EEHTjhZdVh4PSW9Cj3ifufUWjU5thLHuSHRU6vAfrvXw" + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w" ], - "funded": [ - "9j4kVYJwqp8pTGtoWYdmoio4tf6nagJP6hiZwJNHwwcP" - ] + "funded": [] }, - "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU": { - "address": "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", + "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v": { + "address": "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 156 - }, - "first_seen": 1784670576, - "birth_edge": { - "sink": "HvHgoPZxfcBgyWjmSUaByC9WMmDwpZj8qm9rp1cVQ5AU", - "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" - ], - "value": 19924000000, - "signature": "3LziCnYj4ysFF65ZBR3SNn84RCPxARZLuPtZmpzURcX4DxBWNxnJfBLHPy59wws7xzfa1is1EEmFnFBVaKLNBeTo", - "slot": 434383701, - "block_time": 1784670576, - "ambiguous_attribution": true + "Exact": 8835 }, + "first_seen": 1769181912, + "birth_edge": null, "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "BidtDwH9WbEvm9T6tqqsEgAscTzLJeX6H2rGbw22jR39" ], "funded": [] }, - "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh": { - "address": "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh", - "owner": null, + "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ": { + "address": "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 2 + "Exact": 41 }, - "first_seen": 1784388026, + "first_seen": 1783637441, "birth_edge": { - "sink": "HyBcKtGzikuaGKKxdbH6r6a76Rxf4ep71QeDcKGoTUXh", + "sink": "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" ], - "value": 714001614, - "signature": "3zpvvnKV22nNgWn1Ydb4Cmuv1F4YKDfUYamgYGRpiorEU8miy9A1jphqfMqayhc7VCBVGe8rWwa9eid9YshWZ48o", - "slot": 433713663, - "block_time": 1784388026, - "ambiguous_attribution": true + "value": 22952461058, + "signature": "63UUpBFp2VbRkpjKsc3FaXXucqtzjTyPZCsqEtoXBxNiW263FxXHdr6Coo7FksLHC3amGnSZoV9rD9uR4G4yKMfo", + "slot": 431891370, + "block_time": 1783637441, + "ambiguous_attribution": false }, "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" ], - "funded": [ - "FPhsUG4FtDmSZCAv16fhdGquQvymnN8uXUUzuWJLsP6M" - ] + "funded": [] }, - "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd": { - "address": "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd", - "owner": null, + "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE": { + "address": "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 85 + "Exact": 3053 }, - "first_seen": 1718409947, + "first_seen": 1761711549, "birth_edge": { - "sink": "J5fR73GQgDErMSYWU4ZhqdCQMAv4ATKHRYgQRmmeDgwd", + "sink": "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", "sources": [ - "G2YxRa6wt1qePMwfJzdXZG62ej4qaTC7YURzuh2Lwd3t" + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" ], - "value": 36624826000, - "signature": "2obyvsC8f8UtJ8559U2kbcFKJWPo2MfRAWKv7QAJdMvm1hW2W16SLvaJnUWAwEexZPBsZ7s5e2dp38A7mTSPR9Cn", - "slot": 271887897, - "block_time": 1718409947, + "value": 10000000, + "signature": "42BSukNNZwdKRuESCNwP7Dr8xn7HmRc21PNP6PdSa36WrF1mbw2n3CatJwkBzviQiutmRPDn9oCR7bJo4jXDVtzM", + "slot": 376499867, + "block_time": 1761711549, "ambiguous_attribution": false }, "fee_payers": [ - "E9vf42zJXFv8Ljop1cG68NAxLDat4ZEGEWDLfJVX38GF" + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" ], + "funded": [] + }, + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu": { + "address": "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 60000 + }, + "first_seen": 1784207994, + "birth_edge": null, + "fee_payers": [], "funded": [ - "5WhtGkw1u3uC9LxS1CNYsNQqMrkLp5xajmAeJUufNAQ9" + "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn" ] }, - "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw": { - "address": "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw", + "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW": { + "address": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 21 + "Exact": 108 }, - "first_seen": 1784388626, + "first_seen": 1784490766, "birth_edge": { - "sink": "SPHLgtPvqwnpETAnq5b8X9Evqj5g4BLzDFWNZuBFpPw", + "sink": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", "sources": [ - "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG" + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM" ], - "value": 1417608532, - "signature": "4EZV6Ja1sYLxFkLSNn8iEM4HYdBjTiYcQvtxBvJv85YMEqHSAyarQKycubjButPaP8hLhohkykcr3rUZ2xNRhGu1", - "slot": 433715076, - "block_time": 1784388626, + "value": 150000000, + "signature": "2uKVQ2Do2Vandv4rqHhJQZEFKrKQGFQdt15BtdaknzdY8Rxas2Bx3smqkhK6D3Hz8urRCANByPKEJxDcCAKTaq3G", + "slot": 433959006, + "block_time": 1784490766, "ambiguous_attribution": false }, "fee_payers": [ - "HVFUeQwehwmbtwMhrJ7Y7ZRvUtCkC1MSfJTz1aaZerfG" + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM" ], "funded": [] }, - "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW": { - "address": "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW", - "owner": null, + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU": { + "address": "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU", + "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 268 + "Exact": 583 }, - "first_seen": 1774254581, + "first_seen": 1769032382, "birth_edge": { - "sink": "WTFsXATVeP2hYL9k95TVH8wwotPdxVLmkZsNLhq9xsW", + "sink": "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU", "sources": [ - "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi" ], - "value": 993510000, - "signature": "44FY8HTRxTcgN7VSbQRWWMcQKwr1GcJkUrNeRrn37hM2hkkKM2oknZDFKFT51NEERKch9e8SKE6MhNaFGuAvKQaH", - "slot": 408290047, - "block_time": 1774254581, + "value": 20000000, + "signature": "AG1dX11PZCL7yBeTVs4PuNNECWsZ7tuvE94MP16zRMdtgyqchb5gyD512vVb7bd5pk3rYHpfg1RFFCwSLaahc4w", + "slot": 395055111, + "block_time": 1769032382, "ambiguous_attribution": false }, "fee_payers": [ - "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi" ], "funded": [ - "EJnp7CWmmEmnVtm4nmCRX384VxpAYb4hx5WRR41EKkE6" + "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT" ] }, - "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8": { - "address": "fr6yQkDmWy6R6pecbUsxXaw6EvRJznZ2HsK5frQgud8", - "owner": "11111111111111111111111111111111", + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q": { + "address": "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q", + "owner": null, "executable": false, "signatures": { - "AtLeast": 20000 + "Exact": 9987 }, - "first_seen": 1784781685, + "first_seen": 1753767232, "birth_edge": null, - "fee_payers": [], + "fee_payers": [ + "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r" + ], "funded": [ - "7BBMr88hUcH2HQ1YPW9om9WF5TAH8SdhbqTsyvjStDt4" + "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE" ] }, - "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h": { - "address": "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h", - "owner": "11111111111111111111111111111111", + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf": { + "address": "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", + "owner": null, "executable": false, "signatures": { - "Exact": 6573 + "Exact": 2651 }, - "first_seen": 1780811622, + "first_seen": 1737914051, "birth_edge": { - "sink": "isU3XW1Fc8wobkR1nPL1itv9UaknRbFpTNU2bkYJV6h", + "sink": "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", "sources": [ - "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s" + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" ], - "value": 250000000, - "signature": "4Aosp2JL7xc7wzaEFspne89EtdUQ2k5KGX2ifGWFTu148Y9FrvDh8wEuW1wDa7pwMDXLBNbCf6d1z2vXQMLm5ciG", - "slot": 424834280, - "block_time": 1780811622, + "value": 50000000000, + "signature": "2rpBPJocSrWKt2G59e29e8Tj7MJDUAQLEKx2GaoAApyUBLbynToft6iDKYZKTWr2BL9UhfiwV7qr6E1exQn6mnAP", + "slot": 316536578, + "block_time": 1737914051, "ambiguous_attribution": false }, "fee_payers": [ - "51JyiaMJn8BvSMspReePnX5uDiPqfMM4WSKkaPK6o83s" + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" ], - "funded": [] + "funded": [ + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" + ] }, "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe": { "address": "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 1946 + "Exact": 1974 }, "first_seen": 1762263640, "birth_edge": { @@ -2000,53 +4929,55 @@ "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz" ] }, - "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB": { - "address": "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB", + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6": { + "address": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 859 + "Exact": 531 }, - "first_seen": 1784793041, + "first_seen": 1783364451, "birth_edge": { - "sink": "sVwhzH6DgSNuc8q5uBDCrwbSQAEwYub42DrjgMqQehB", + "sink": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", "sources": [ - "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2" + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" ], - "value": 97997217398, - "signature": "NUEDXVJvG4JREhaSjMytzbdG5Bm9CucuFuyCj9evrfefAkE79RzFyr38HcuvGEkG9c2SZUTQJtNPTssxoZugU4q", - "slot": 434675365, - "block_time": 1784793041, + "value": 1100000000, + "signature": "4kLBV2LbExCtK85mRP4FnNGDaHVTBNNvvtyQjbFVqiqeLgvs3fcSUVbsxtmuZ6WzVUBYV9h9GJzyUuWpYf6nAxTD", + "slot": 431221166, + "block_time": 1783364451, "ambiguous_attribution": false }, "fee_payers": [ - "9xxr2L8nTGGaoa2LVEen4GGTdFB5585kHAVPftAhLzF2" + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" ], "funded": [] }, - "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6": { - "address": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx": { + "address": "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 531 + "Exact": 74 }, - "first_seen": 1783364451, + "first_seen": 1776857831, "birth_edge": { - "sink": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "sink": "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx", "sources": [ - "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" ], - "value": 1100000000, - "signature": "4kLBV2LbExCtK85mRP4FnNGDaHVTBNNvvtyQjbFVqiqeLgvs3fcSUVbsxtmuZ6WzVUBYV9h9GJzyUuWpYf6nAxTD", - "slot": 431221166, - "block_time": 1783364451, + "value": 1000000000, + "signature": "4mEmeFnTKjqyT3XM3UZbp7fGA5gWeMB8ny87kZJbBbGGW89NJv5i5GBSvAJqx4JQN4eKDKNUomka7gE4AvgLRm8v", + "slot": 414902368, + "block_time": 1776857831, "ambiguous_attribution": false }, "fee_payers": [ - "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" ], - "funded": [] + "funded": [ + "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do" + ] } } } \ No newline at end of file diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index f7260085..e53d0741 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -78,6 +78,35 @@ that steals escrow. This is a named trust assumption, not a property. `solana program set-upgrade-authority --final` removes it and correspondingly removes the ability to fix anything. +### The pool cannot be one of its own action's accounts + +The vault authorises an action through its seeds, but it is never one of the +callee's accounts. It cannot be: settlement moves the payout out of the vault by +direct mutation before invoking, and handing that same account to a callee makes +the runtime reconcile those lamports across the CPI boundary and reject the whole +instruction as unbalanced — whether the account is marked writable or not. + +So an action whose target needs the pool itself as an account is not expressible +in this version. Value reaches an action through the beneficiary instead. A +settler that tries to place the vault in the action's account list is refused +rather than left to fail later. + +### The action's account list is chosen by the settler + +The proof binds the selector, the target program, the relay fee, the payload and +**how many** accounts the action takes. It does not bind **which** accounts fill +those slots — settlement is permissionless, so whoever settles picks them. + +For a target whose destination is instruction data this changes nothing. For a +target whose destination is an *account* — an SPL token transfer, for instance — +a settler could point the action at accounts the member did not choose. The +member's own escrow is not at risk, because only this program can debit the +vault, but anything the action itself would move is. + +Binding an account-list commitment into the proof would close this. It is not +implemented, and the claim elsewhere that "a relay cannot redirect an action" is +about the selector, target, fee and payload, not about the account list. + ### Timing at submission Settlement batches payouts, but `submit_spend` is a transaction at a time of the diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 60d7093d..762816e1 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -586,9 +586,15 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro /// through this pool is identical whoever asked for it. /// /// The payload and the target were both fixed at submit time and bound into the -/// proof, so a settler chooses neither. What a settler does supply is the -/// account list, and the accounts are the target program's problem to validate -/// — exactly as they would be for any caller. +/// proof, so a settler chooses neither, and the declared account count is bound +/// too. What a settler still supplies is *which* accounts fill those slots, and +/// those are the target program's problem to validate — exactly as they would be +/// for any caller. For a target whose destination is an account rather than +/// instruction data, that is a real limit, and the threat model says so. +/// +/// The vault is appended by this program as the final account and marked signer. +/// A settler that tries to place it in the action's own list is refused, because +/// the same account appearing twice has its lamport change applied twice. #[allow(clippy::too_many_arguments)] fn invoke_action<'a>( program_id: &Pubkey, @@ -628,11 +634,28 @@ fn invoke_action<'a>( // to act on. The vault signs, so the funds visibly come from the pool. move_lamports(vault_account, beneficiary, payout)?; + // The vault authorises the call through its seeds but is deliberately not + // one of the callee's accounts. + // + // This program has already moved the payout out of the vault by direct + // mutation. Handing that same account to a callee makes the runtime + // reconcile those lamports across the CPI boundary, and it rejects the whole + // instruction as unbalanced — with the vault marked writable or not. So the + // vault reaches `invoke_signed` in the account infos, which is what lets its + // seeds sign, and never appears in the instruction's account list. + // + // The consequence is a real limit, and THREAT_MODEL.md states it: an action + // whose target needs the pool itself as one of its accounts is not + // expressible here. Value reaches the action through the beneficiary. + if action_infos.iter().any(|a| a.key == vault_account.key) { + return Err(MirrorProgramError::MalformedInstruction.into()); + } + let metas: Vec = action_infos .iter() .map(|a| solana_program::instruction::AccountMeta { pubkey: *a.key, - is_signer: a.is_signer || a.key == vault_account.key, + is_signer: a.is_signer, is_writable: a.is_writable, }) .collect(); @@ -643,9 +666,16 @@ fn invoke_action<'a>( data: payload, }; + // The vault and the target may already be among the action's accounts — the + // vault whenever the action needs the pool to sign for it, which is the + // normal case. Appending them unconditionally puts the same account in the + // list twice, and the runtime then reconciles its lamports against itself + // and fails the whole instruction as unbalanced. let mut infos = action_infos.to_vec(); infos.push(vault_account.clone()); - infos.push(target_info.clone()); + if !infos.iter().any(|a| a.key == target_info.key) { + infos.push(target_info.clone()); + } invoke_signed( &ix, diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index 97736f05..9344d334 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -548,6 +548,18 @@ fn settle_ix_with_targets( batch: &[(Pubkey, Pubkey, Keypair)], settler: &Pubkey, targets: &[Option], +) -> Instruction { + settle_ix_full(env, batch, settler, targets, &[]) +} + +/// Settlement carrying, per spend, an optional target program and that action's +/// own account list — the shape a real CPI needs. +fn settle_ix_full( + env: &Env, + batch: &[(Pubkey, Pubkey, Keypair)], + settler: &Pubkey, + targets: &[Option], + action_accounts: &[Vec], ) -> Instruction { let mut metas = vec![ AccountMeta::new(*settler, true), @@ -561,6 +573,9 @@ fn settle_ix_with_targets( if let Some(Some(target)) = targets.get(i) { metas.push(AccountMeta::new_readonly(*target, false)); } + if let Some(accounts) = action_accounts.get(i) { + metas.extend(accounts.iter().cloned()); + } } Instruction::new_with_bytes( env.program_id, @@ -858,6 +873,29 @@ fn action_ix( beneficiary: &Pubkey, relay: &Pubkey, payload: &[u8], +) -> Instruction { + action_ix_n( + env, + proof, + nullifier, + target, + beneficiary, + relay, + 0, + payload, + ) +} + +#[allow(clippy::too_many_arguments)] +fn action_ix_n( + env: &Env, + proof: &mirror_circuit::SolanaProof, + nullifier: [u8; 32], + target: &Pubkey, + beneficiary: &Pubkey, + relay: &Pubkey, + action_accounts: u8, + payload: &[u8], ) -> Instruction { let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); Instruction::new_with_bytes( @@ -872,7 +910,7 @@ fn action_ix( target_program: target.to_bytes(), beneficiary: beneficiary.to_bytes(), relay_fee: RELAY_FEE, - action_accounts: 0, + action_accounts, payload: payload.to_vec(), } .pack(), @@ -893,6 +931,20 @@ fn action_proof( target: &Pubkey, beneficiary: &Pubkey, payload: &[u8], +) -> mirror_circuit::SolanaProof { + action_proof_n(keys, tree, notes, index, target, beneficiary, 0, payload) +} + +#[allow(clippy::too_many_arguments)] +fn action_proof_n( + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + target: &Pubkey, + beneficiary: &Pubkey, + action_accounts: u8, + payload: &[u8], ) -> mirror_circuit::SolanaProof { use ark_std::rand::SeedableRng; let merkle_proof = tree.proof(index as u64).unwrap(); @@ -901,7 +953,7 @@ fn action_proof( &target.to_bytes(), &beneficiary.to_bytes(), RELAY_FEE, - 0, + action_accounts, payload, ); let witness = Witness { @@ -975,6 +1027,116 @@ fn a_crowd_of_members_perform_a_real_protocol_action_together() { } } +/// The action path with a non-empty account list, which nothing exercised +/// before: every action test passed zero accounts, so the loop that reads them +/// and the metas built from them were dead code. +/// +/// SPL Memo requires every account handed to it to have signed, so passing one +/// makes the CPI's account list load-bearing rather than decorative — if the +/// program dropped an account, mislabelled its signer flag, or miscounted, Memo +/// rejects. +#[test] +fn an_action_runs_with_a_non_empty_account_list() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let memo: Pubkey = MEMO_PROGRAM.parse().unwrap(); + env.svm.add_program(memo, &memo_bytes()).unwrap(); + + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let payload = b"signed by the pool".as_slice(); + + // One action account: the vault itself, which Memo will require to have + // signed. + // One action account, declared in the proof and therefore bound. + let proof = action_proof_n(&keys, &tree, ¬es, 0, &memo, &beneficiary, 1, payload); + let nullifier = proof.public_inputs[1]; + let ix = action_ix_n( + &env, + &proof, + nullifier, + &memo, + &beneficiary, + &relay.pubkey(), + 1, + payload, + ); + env.send(ix, &relay).expect("submitting the action"); + + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + + let mut clock = env.svm.get_sysvar::(); + clock.unix_timestamp += mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS + 1; + env.svm.set_sysvar(&clock); + + // The settler itself is the action's account. It signs the transaction, so + // Memo's requirement that every account has signed is genuinely satisfied + // through the program's metas rather than by Memo having nothing to check. + let batch = vec![(spend_pda, beneficiary, relay.insecure_clone())]; + let ix = settle_ix_full( + &env, + &batch, + &settler.pubkey(), + &[Some(memo)], + &[vec![AccountMeta::new_readonly(settler.pubkey(), true)]], + ); + let cu = env.send_expect_cu(ix, &settler); + println!("settled a signed CPI action, {cu} CU"); + + let mut data = env.svm.get_account(&spend_pda).unwrap().data; + let record = Spend::load(&mut data).unwrap(); + assert_eq!(record.status(), STATUS_SETTLED); + assert_eq!(record.action_accounts(), 1); + assert_eq!(record.payload(), payload); +} + +/// A settler must not be able to hand the action a different account list than +/// the member declared, because the count is now inside the binding. +#[test] +fn a_settler_cannot_change_how_many_accounts_an_action_gets() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let memo: Pubkey = MEMO_PROGRAM.parse().unwrap(); + env.svm.add_program(memo, &memo_bytes()).unwrap(); + + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let payload = b"one account".as_slice(); + + let proof = action_proof_n(&keys, &tree, ¬es, 1, &memo, &beneficiary, 1, payload); + let nullifier = proof.public_inputs[1]; + let ix = action_ix_n( + &env, + &proof, + nullifier, + &memo, + &beneficiary, + &relay.pubkey(), + 1, + payload, + ); + env.send(ix, &relay).expect("submitting"); + + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let mut clock = env.svm.get_sysvar::(); + clock.unix_timestamp += mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS + 1; + env.svm.set_sysvar(&clock); + + // Supply no action accounts where the record declares one. The program reads + // one account past this spend's own, so it consumes something that is not + // there and fails rather than invoking with a truncated list. + let batch = vec![(spend_pda, beneficiary, relay.insecure_clone())]; + let ix = settle_ix_full(&env, &batch, &settler.pubkey(), &[Some(memo)], &[vec![]]); + let err = env + .send(ix, &settler) + .expect_err("settlement invoked with fewer accounts than declared"); + println!("truncated action account list rejected: {err}"); +} + #[test] fn a_relay_cannot_swap_the_target_program() { let (mut env, tree, notes, keys) = seeded_pool(6); From 0f0368b3e462db66236100ff52d9209b033697d0 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 17:44:03 -0300 Subject: [PATCH 27/83] docs: correct every falsifiable claim a reader can check A documentation fact-check against the source found twelve statements a judge could disprove by running one command. Corrected: - the test count said 152; it is 179 - the headline compute figure said 99k; the only number this repo measures is submit_spend at 97,860 CU on a real SVM. The 74,179 + 5,661N table in the Groth16 notes comes from a scratch circuit that is not in this repository, which the file now states - four docs credited the pool PDA with signing actions; invoke_signed uses the vault's seeds, and one file contradicted itself two sections apart - "Actions carry one signer" stopped being true when the vault stopped being one of the callee's accounts; it is now "one caller", which is what holds - the arity table listed the action binding as an arity-4 Poseidon; it is a keccak digest, as the code's own comment already said - "all three checked against circomlib" was true of two. The gadget and the host are pinned to the published vector; the syscall is checked against the host on-chain. The payoff clause did not follow from the premise - the binding's field list appeared three times, each missing a different field - two CLI command lists omitted verify-setup and soak, including the README table, while the README leans on verify-setup elsewhere - "Two commands" introduced three - the README promised a headline recomputable from a file that does not exist. The committed artifact is data/sample-privacycash.json, and it recomputes a refusal rather than a headline, which the README now says - a source comment claimed a replay costs about 3k compute units; measured, 11k Also removes sp4: twenty-six files of vendored solana-program source, committed by accident while inspecting an API and referenced by nothing. --- .gitignore | 1 + README.md | 34 +- crates/mirror-circuit/src/poseidon_gadget.rs | 4 +- crates/mirror-core/src/hash.rs | 8 +- docs/ARCHITECTURE.md | 32 +- docs/GROTH16_INTEGRATION.md | 5 +- docs/PLAN.md | 2 +- docs/THREAT_MODEL.md | 23 +- programs/mirror-pool/src/processor.rs | 2 +- sp4/solana-program-4.0.0/.cargo_vcs_info.json | 6 - sp4/solana-program-4.0.0/Cargo.lock | 1912 ----------------- sp4/solana-program-4.0.0/Cargo.toml | 287 --- sp4/solana-program-4.0.0/Cargo.toml.orig | 113 - sp4/solana-program-4.0.0/README.md | 15 - sp4/solana-program-4.0.0/src/bpf_loader.rs | 23 - .../src/bpf_loader_deprecated.rs | 13 - sp4/solana-program-4.0.0/src/compute_units.rs | 13 - .../src/ed25519_program.rs | 4 - .../src/entrypoint_deprecated.rs | 139 -- .../src/epoch_schedule.rs | 8 - sp4/solana-program-4.0.0/src/hash.rs | 11 - sp4/solana-program-4.0.0/src/incinerator.rs | 2 - sp4/solana-program-4.0.0/src/instruction.rs | 52 - sp4/solana-program-4.0.0/src/lamports.rs | 6 - sp4/solana-program-4.0.0/src/lib.rs | 690 ------ sp4/solana-program-4.0.0/src/log.rs | 101 - sp4/solana-program-4.0.0/src/program.rs | 215 -- sp4/solana-program-4.0.0/src/program_error.rs | 14 - .../src/secp256k1_program.rs | 9 - sp4/solana-program-4.0.0/src/slot_hashes.rs | 1 - sp4/solana-program-4.0.0/src/slot_history.rs | 2 - .../src/syscalls/definitions.rs | 39 - sp4/solana-program-4.0.0/src/syscalls/mod.rs | 25 - sp4/solana-program-4.0.0/src/sysvar.rs | 33 - sp4/sp.crate | Bin 30262 -> 0 bytes 35 files changed, 69 insertions(+), 3775 deletions(-) delete mode 100644 sp4/solana-program-4.0.0/.cargo_vcs_info.json delete mode 100644 sp4/solana-program-4.0.0/Cargo.lock delete mode 100644 sp4/solana-program-4.0.0/Cargo.toml delete mode 100644 sp4/solana-program-4.0.0/Cargo.toml.orig delete mode 100644 sp4/solana-program-4.0.0/README.md delete mode 100644 sp4/solana-program-4.0.0/src/bpf_loader.rs delete mode 100644 sp4/solana-program-4.0.0/src/bpf_loader_deprecated.rs delete mode 100644 sp4/solana-program-4.0.0/src/compute_units.rs delete mode 100644 sp4/solana-program-4.0.0/src/ed25519_program.rs delete mode 100644 sp4/solana-program-4.0.0/src/entrypoint_deprecated.rs delete mode 100644 sp4/solana-program-4.0.0/src/epoch_schedule.rs delete mode 100644 sp4/solana-program-4.0.0/src/hash.rs delete mode 100644 sp4/solana-program-4.0.0/src/incinerator.rs delete mode 100644 sp4/solana-program-4.0.0/src/instruction.rs delete mode 100644 sp4/solana-program-4.0.0/src/lamports.rs delete mode 100644 sp4/solana-program-4.0.0/src/lib.rs delete mode 100644 sp4/solana-program-4.0.0/src/log.rs delete mode 100644 sp4/solana-program-4.0.0/src/program.rs delete mode 100644 sp4/solana-program-4.0.0/src/program_error.rs delete mode 100644 sp4/solana-program-4.0.0/src/secp256k1_program.rs delete mode 100644 sp4/solana-program-4.0.0/src/slot_hashes.rs delete mode 100644 sp4/solana-program-4.0.0/src/slot_history.rs delete mode 100644 sp4/solana-program-4.0.0/src/syscalls/definitions.rs delete mode 100644 sp4/solana-program-4.0.0/src/syscalls/mod.rs delete mode 100644 sp4/solana-program-4.0.0/src/sysvar.rs delete mode 100644 sp4/sp.crate diff --git a/.gitignore b/.gitignore index 9dd59292..7fcdd2e2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ target/ keys/ *.keypair.json notes/ +sp4/ diff --git a/README.md b/README.md index d7717f9c..82995348 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ fixed by a better circuit. What it can be is *measured*, and measured honestly. So this submission claims exactly two things: -1. **The action side is closed.** Actions execute from the pool PDA, so an +1. **The action side is closed.** Actions execute from the pool's vault PDA, so an action's on-chain funding trace leads to the pool and is identical for every member. 2. **The membership side is measured**, from real mainnet data, with the method @@ -39,13 +39,13 @@ not say. There is a section below of things we deliberately do not claim. | | | |---|---| -| `programs/mirror-pool` | The on-chain program. Groth16 verified on-chain at **99k CU**. | +| `programs/mirror-pool` | The on-chain program. `submit_spend`, proof and all, measured at **97,860 CU** on a real SVM. | | `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain. | | `crates/mirror-circuit` | R1CS gadget, membership circuit, prover, key export. | | `crates/mirror-provenance` | The funding-provenance measurement. | -| `crates/mirror-cli` | `setup`, `check-endpoint`, `seeds`, `collect`, `analyze`. | +| `crates/mirror-cli` | `setup`, `verify-setup`, `check-endpoint`, `seeds`, `collect`, `analyze`, `soak`. | -**152 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**179 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -87,25 +87,29 @@ settlement is permissionless, so they settle their own batch once the timeout passes. The cost is the expected one — their wallet signs, giving up anonymity — and the test pins that the exit works. -**Gadget, host and syscall compute one hash.** All three are checked against -circomlib's published `poseidon([1,2])` vector rather than against each other, so -all three agreeing on a wrong answer is not reachable. Several published Solana -projects ship a gadget whose native and in-circuit hashes differ; that only -surfaces at proving time. +**Gadget, host and syscall compute one hash.** The gadget and the host are each +pinned to circomlib's published `poseidon([1,2])` vector rather than to each +other, so the two agreeing on a wrong answer would need circomlib's own vector to +be wrong. The syscall is then checked against the host on-chain: the end-to-end +suite asserts the root the deployed program builds equals the root the host +built. Several published Solana projects ship a gadget whose native and +in-circuit hashes differ; that only surfaces at proving time. ## The measurement -Two commands, and the split is the point: +Three commands, two passes, and the split is the point: ``` -mirror seeds --program # member-weighted frame, one row per depositor +mirror seeds --program # member-weighted frame, one row per depositor mirror collect --seeds seeds.txt # the only networked step; writes sample.json mirror analyze --sample sample.json # pure, offline, deterministic ``` -`sample.json` is the committed artifact. Anyone holding it recomputes the -headline without RPC access and without trusting that our endpoint behaved the -same way on their machine. +`data/sample-privacycash.json` is the committed artifact. Anyone holding it +recomputes the result without RPC access and without trusting that our endpoint +behaved the same way on their machine — including the fact that this collection +came back above the 1% RPC-failure limit and so yields no headline. +`docs/MEASUREMENT_LOG.md` records every run. ### Design choices that exist to avoid specific published defects @@ -191,4 +195,6 @@ selecting rather than reporting. | `docs/PROVENANCE_METHOD.md` | Adversary model, metrics, sampling, the honest-claims analysis. | | `docs/GROTH16_INTEGRATION.md` | The arkworks-to-Solana byte layout, verified by execution. | | `docs/MEASUREMENT_LOG.md` | Every run. | +| `docs/THREAT_MODEL.md` | The adversary, what holds, and every place it stops. | +| `docs/PROOF.md` | Devnet signatures for every flow, and the rejections. | | `docs/PLAN.md` | What was planned, and what was cut. | diff --git a/crates/mirror-circuit/src/poseidon_gadget.rs b/crates/mirror-circuit/src/poseidon_gadget.rs index 06943311..96147c0b 100644 --- a/crates/mirror-circuit/src/poseidon_gadget.rs +++ b/crates/mirror-circuit/src/poseidon_gadget.rs @@ -162,8 +162,8 @@ mod tests { } /// Anchors the gadget to the same external constant `mirror-core` pins, so - /// gadget, host and syscall are each checked against circomlib rather than - /// against each other. + /// gadget and host are each checked against circomlib rather than against each + /// other, and the syscall is checked against the host on-chain. #[test] fn matches_the_published_circomlib_vector() { let (got, _) = in_circuit(&[Fr::from(1u64), Fr::from(2u64)]); diff --git a/crates/mirror-core/src/hash.rs b/crates/mirror-core/src/hash.rs index 2b5e6b50..34639cb5 100644 --- a/crates/mirror-core/src/hash.rs +++ b/crates/mirror-core/src/hash.rs @@ -298,10 +298,10 @@ mod tests { /// `poseidon([1, 2])` under circomlib's BN254 x5 instance is the published /// constant /// `7853200120776062878684798364095072458815029376092732009249414926327459813530`. - /// Pinning it here means the host, the syscall and the R1CS gadget in - /// `mirror-circuit` are each checked against an external published value - /// rather than against each other — so all three agreeing on a wrong answer - /// is not a reachable state. + /// Pinning it here means the host and the R1CS gadget in `mirror-circuit` are + /// each checked against an external published value rather than against each + /// other. The syscall is then checked against the host on-chain, by the + /// end-to-end suite asserting the deployed program's root equals the host's. #[test] fn poseidon2_matches_the_published_circomlib_vector() { const CIRCOMLIB_1_2: [u8; 32] = [ diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index de820fe8..e59f8f5d 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -14,7 +14,7 @@ mirror-core field, Poseidon, Merkle accumulator, notes (linked on-chain mirror-circuit R1CS gadget, membership circuit, prover (host only) mirror-pool the on-chain program mirror-provenance funding-provenance measurement (host only) -mirror-cli setup, endpoint check, collect, analyze +mirror-cli setup, verify-setup, endpoint check, seeds, collect, analyze, soak ``` `mirror-core` is shared by the program and the host deliberately: a commitment, @@ -50,7 +50,10 @@ itself a domain separator and a free one: | nullifier | 1 | | Merkle node | 2 | | note commitment | 3 | -| action binding | 4 | + +The action binding is not in this table. Its payload is variable length and +Poseidon is a fixed-arity compression, so the binding is a keccak digest under +the domain tag `mirror-pool:action:v1`, masked into the field. An integer tag was the first design and it was wrong: with a small tag constant, a Merkle node whose left child equals the tag collides with a nullifier. @@ -79,7 +82,9 @@ an unspent note. with root `R`, my nullifier is `H1(k)`, and this proof is bound to `action`.* Three public inputs, and that is a cost decision. On-chain verification measures -at `74,179 + 5,661 × N` compute units, so each input costs about 5.7k CU. +as `74,179 + 5,661 × N` compute units, so each input costs about 5.7k CU. See +`GROTH16_INTEGRATION.md`; the figure this repository reproduces directly is the +whole `submit_spend` instruction at 97,860 CU. | public input | why it cannot be a witness | |---|---| @@ -106,11 +111,13 @@ on-chain and to light-poseidon off-chain, so host and program agree by construction. The R1CS gadget then reads light-poseidon's published round constants rather than re-deriving them. -All three are checked against circomlib's published `poseidon([1,2])` vector -rather than against each other, so all three agreeing on a wrong answer is not a -reachable state. Several published Solana projects ship a gadget whose native and -in-circuit hashes are different functions; that failure only appears at proving -time, and this is the test that catches it. +The gadget and the host are each checked against circomlib's published +`poseidon([1,2])` vector rather than against each other, and the syscall is then +checked against the host on-chain — the end-to-end suite asserts the root the +deployed program builds equals the root the host built. Several published Solana +projects ship a gadget whose native and in-circuit hashes are different +functions; that failure only appears at proving time, and this is the test that +catches it. A pure-Rust Poseidon on SBF overflows the 4 KB stack frame and costs roughly 1,500× the syscall even where codegen lets it complete, so no arkworks code is @@ -130,7 +137,8 @@ never from the instruction, and appends the commitment to the accumulator. records the authorised action. Pays out nothing. The action binding is never transmitted. It is recomputed on-chain from the -selector, the beneficiary and the relay fee and used as the third public input, +selector, the target program, the beneficiary, the relay fee, the declared +account count and the payload, and used as the third public input, so a relay that alters any of them produces a different binding and the pairing fails. There is no separate field that could be checked incorrectly. @@ -138,6 +146,10 @@ The relay signs, never the member. A member paying their own fee would sign with their own wallet and destroy their own anonymity, so no member key appears on chain on this path. +Two limits follow, and `docs/THREAT_MODEL.md` states them: the binding fixes +*how many* accounts an action takes but not *which* ones, and the vault can never +be one of the callee's own accounts. + **`settle_epoch`** — executes a batch in one transaction so every payout shares a timestamp and an ordering. @@ -169,7 +181,7 @@ where its users' money came from. So the protocol does two things about it, and claims exactly those two: -1. **The action side is closed.** Actions are executed by the pool PDA, so the +1. **The action side is closed.** Actions are executed by the pool's vault PDA, so the on-chain funding trace of an action leads to the pool and is identical for every member. 2. **The membership side is measured.** `mirror-provenance` computes it from real diff --git a/docs/GROTH16_INTEGRATION.md b/docs/GROTH16_INTEGRATION.md index 2bcbfcf4..ba049739 100644 --- a/docs/GROTH16_INTEGRATION.md +++ b/docs/GROTH16_INTEGRATION.md @@ -56,7 +56,10 @@ than a slice. ## Compute units -Measured in LiteSVM against a real SBF build: +Measured in LiteSVM against a real SBF build, with a scratch circuit per input +count that is not part of this repository. The figure this repository does +reproduce is the whole `submit_spend` instruction at three inputs — 97,860 CU, +printed by `cargo test -p mirror-pool-program --test end_to_end -- --nocapture`. | Public inputs | `verify()` | |---|---| diff --git a/docs/PLAN.md b/docs/PLAN.md index 432d19af..59dd99a7 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -17,7 +17,7 @@ We do not claim to hide provenance. Value moves one way on a ledger, and no deposit pool controls where its users' money came from. What we claim is narrower and defensible: -1. **The action side is closed.** Actions are executed by the pool PDA, so the +1. **The action side is closed.** Actions are executed by the pool's vault PDA, so the on-chain funding trace of an action leads to the pool and is identical for every member. 2. **The membership side is measured, not asserted.** Effective anonymity is diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index e53d0741..479787f8 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -26,12 +26,15 @@ pool's notes. settler-signed. A member's wallet never touches the protocol after depositing. **A relay cannot alter what was authorised.** The action binding covers the -selector, the target program, the beneficiary, the relay fee and the payload, and -is recomputed on-chain rather than transmitted. +selector, the target program, the beneficiary, the relay fee, the declared +account count and the payload, under a domain tag, and is recomputed on-chain +rather than transmitted. -**Actions carry one signer.** The pool PDA invokes on every member's behalf, so -the on-chain trace of a stake made through the pool is identical whoever asked -for it. +**Actions carry one caller.** Every action is invoked by the pool program on a +member's behalf and funded out of the pool's vault, so the on-chain trace of a +stake made through the pool is identical whoever asked for it. The vault +authorises the CPI through its seeds but is never one of the callee's accounts — +see below. **Payouts share a timestamp.** Settlement batches, so arrival time does not separate members within a batch. @@ -54,7 +57,7 @@ survives is the size of the class rather than `k`. **This is not closed and cannot be by a better circuit.** No deposit pool controls where its users' money came from. What we do instead: -- the *action* side is closed, because actions execute from the pool PDA; +- the *action* side is closed, because actions execute from the pool's vault PDA; - the *membership* side is measured from real chain data, and the method, the failure census and the sampling frame are published beside the number. @@ -93,8 +96,8 @@ rather than left to fail later. ### The action's account list is chosen by the settler -The proof binds the selector, the target program, the relay fee, the payload and -**how many** accounts the action takes. It does not bind **which** accounts fill +The proof binds the selector, the target program, the beneficiary, the relay +fee, the payload and **how many** accounts the action takes. It does not bind **which** accounts fill those slots — settlement is permissionless, so whoever settles picks them. For a target whose destination is instruction data this changes nothing. For a @@ -130,7 +133,9 @@ measurement is the honest reading of what the set is worth. ### Not audited No external review. The end-to-end suite runs against the compiled program on a -real SVM and the negative cases carry this program's own error codes, which is +real SVM and every negative case but one carries this program's own error code — +the truncated-account-list case is caught by the runtime before the program can +rule on it — which is evidence of behaviour and not a substitute for an audit. ## Deliberate non-goals diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 762816e1..ea33fe6a 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -295,7 +295,7 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest // // A spent nullifier is knowable from one account read; verifying a proof // costs about 95k compute units. Checking the cheap thing first means a - // replay is rejected at roughly 3k CU instead of 95k, which matters because + // replay is rejected at roughly 11k CU instead of 95k, which matters because // replays are exactly what an attacker submits in bulk. // // It is also an explicit check rather than a reliance on account creation diff --git a/sp4/solana-program-4.0.0/.cargo_vcs_info.json b/sp4/solana-program-4.0.0/.cargo_vcs_info.json deleted file mode 100644 index 89cd2758..00000000 --- a/sp4/solana-program-4.0.0/.cargo_vcs_info.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "git": { - "sha1": "12b63164cc1854b798362190583de5d1f2fe8091" - }, - "path_in_vcs": "program" -} \ No newline at end of file diff --git a/sp4/solana-program-4.0.0/Cargo.lock b/sp4/solana-program-4.0.0/Cargo.lock deleted file mode 100644 index bfc1a68a..00000000 --- a/sp4/solana-program-4.0.0/Cargo.lock +++ /dev/null @@ -1,1912 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dde20b3d026af13f561bdd0f15edf01fc734f0dafcedbaf42bba506a9517f223" -dependencies = [ - "derive_arbitrary", -] - -[[package]] -name = "arrayref" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" - -[[package]] -name = "arrayvec" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[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 = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - -[[package]] -name = "bitflags" -version = "2.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2261d10cca569e4643e526d8dc2e62e433cc8aba21ab764233731f8d369bf394" - -[[package]] -name = "bitmaps" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" -dependencies = [ - "typenum", -] - -[[package]] -name = "blake3" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e" -dependencies = [ - "arrayref", - "arrayvec", - "cc", - "cfg-if", - "constant_time_eq", -] - -[[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 = "borsh" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5430e3be710b68d984d1391c854eb431a9d548640711faa54eecb1df93db91cc" -dependencies = [ - "borsh-derive", - "cfg_aliases", -] - -[[package]] -name = "borsh-derive" -version = "1.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8b668d39970baad5356d7c83a86fee3a539e6f93bf6764c97368243e17a0487" -dependencies = [ - "once_cell", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "boxcar" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bb12751a83493ef4b8da1120451a262554e216a247f14b48cb5e8fe7ed8bdf" - -[[package]] -name = "bs58" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "bv" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8834bb1d8ee5dc048ee3124f2c7c1afcc6bc9aed03f11e9dfd8c69470a5db340" -dependencies = [ - "feature-probe", - "serde", -] - -[[package]] -name = "bytemuck" -version = "1.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" - -[[package]] -name = "bytemuck_derive" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fa76293b4f7bb636ab88fd78228235b5248b4d05cc589aed610f954af5d7c7a" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" - -[[package]] -name = "cc" -version = "1.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4730490333d58093109dc02c23174c3f4d490998c3fed3cc8e82d57afedb9cf" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - -[[package]] -name = "const-oid" -version = "0.9.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" - -[[package]] -name = "constant_time_eq" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" - -[[package]] -name = "cpufeatures" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" -dependencies = [ - "libc", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" -dependencies = [ - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" - -[[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 0.6.4", - "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 = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest", - "fiat-crypto", - "rand_core 0.6.4", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "darling" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", - "serde", -] - -[[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 = "derive_arbitrary" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30542c1ad912e0e3d22a1935c290e12e8a29d704a420177a31faad4a601a0800" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[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 = "ecdsa" -version = "0.16.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" -dependencies = [ - "der", - "digest", - "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", - "ff", - "generic-array", - "group", - "pkcs8", - "rand_core 0.6.4", - "sec1", - "subtle", - "zeroize", -] - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "feature-probe" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "835a3dc7d1ec9e75e2b5fb4ba75396837112d2060b03f7d43bc1897c7f7211da" - -[[package]] -name = "ff" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" -dependencies = [ - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - -[[package]] -name = "five8" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23f76610e969fa1784327ded240f1e28a3fd9520c9cec93b636fcf62dd37f772" -dependencies = [ - "five8_core", -] - -[[package]] -name = "five8_const" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a0f1728185f277989ca573a402716ae0beaaea3f76a8ff87ef9dd8fb19436c5" -dependencies = [ - "five8_core", -] - -[[package]] -name = "five8_core" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "059c31d7d36c43fe39d89e55711858b4da8be7eb6dabac23c7289b1a19489406" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[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 = "getrandom" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" -dependencies = [ - "cfg-if", - "libc", - "r-efi", - "wasip2", -] - -[[package]] -name = "group" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" -dependencies = [ - "ff", - "rand_core 0.6.4", - "subtle", -] - -[[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" - -[[package]] -name = "hmac" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" -dependencies = [ - "digest", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "im" -version = "15.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9" -dependencies = [ - "bitmaps", - "rand_core 0.6.4", - "rand_xoshiro", - "rayon", - "serde", - "sized-chunks", - "typenum", - "version_check", -] - -[[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", -] - -[[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", - "signature", -] - -[[package]] -name = "keccak" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" -dependencies = [ - "cpufeatures", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.170" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" - -[[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 = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" -dependencies = [ - "autocfg", -] - -[[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-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.21.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" - -[[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 = "pastey" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b867cad97c0791bbd3aaa6472142568c6c9e8f71937e98379f584cfb0cf35bec" - -[[package]] -name = "pkcs8" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" -dependencies = [ - "der", - "spki", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[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-macro2" -version = "1.0.106" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "qualifier_attr" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e2e25ee72f5b24d773cae88422baddefff7714f97aab68d96fe2b6fc4a28fb2" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "quote" -version = "1.0.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "r-efi" -version = "5.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" - -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.15", -] - -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.4", -] - -[[package]] -name = "rand_xoshiro" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa" -dependencies = [ - "rand_core 0.6.4", -] - -[[package]] -name = "rayon" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" -dependencies = [ - "bitflags", -] - -[[package]] -name = "rfc6979" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" -dependencies = [ - "hmac", - "subtle", -] - -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - -[[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 = "semver" -version = "1.0.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" - -[[package]] -name = "serde" -version = "1.0.226" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dca6411025b24b60bfa7ec1fe1f8e710ac09782dca409ee8237ba74b51295fd" -dependencies = [ - "serde_core", - "serde_derive", -] - -[[package]] -name = "serde_core" -version = "1.0.226" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba2ba63999edb9dac981fb34b3e5c0d111a69b0924e253ed29d83f7c99e966a4" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.226" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8db53ae22f34573731bafa1db20f04027b2d25e02d8205921b569171699cdb33" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_with" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" -dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "sha2-const-stable" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f179d4e11094a893b82fff208f74d448a7512f99f5a0acbd5c679b705f83ed9" - -[[package]] -name = "sha3" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" -dependencies = [ - "digest", - "keccak", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signature" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core 0.6.4", -] - -[[package]] -name = "sized-chunks" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e" -dependencies = [ - "bitmaps", - "typenum", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "solana-account-info" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc3397241392f5756925029acaa8515dc70fcbe3d8059d4885d7d6533baf64fd" -dependencies = [ - "bincode", - "serde_core", - "solana-address 2.2.0", - "solana-program-error", - "solana-program-memory", -] - -[[package]] -name = "solana-address" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2ecac8e1b7f74c2baa9e774c42817e3e75b20787134b76cc4d45e8a604488f5" -dependencies = [ - "solana-address 2.2.0", -] - -[[package]] -name = "solana-address" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68c5d02824391b072dc5cd0aaa85fb0af9784a21d23286a767994d1e8a322131" -dependencies = [ - "arbitrary", - "borsh", - "bytemuck", - "bytemuck_derive", - "curve25519-dalek", - "five8", - "five8_const", - "rand", - "serde", - "serde_derive", - "sha2-const-stable", - "solana-atomic-u64", - "solana-define-syscall 5.0.0", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-program-error", - "solana-sanitize", - "solana-sha256-hasher", - "wincode", -] - -[[package]] -name = "solana-atomic-u64" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a933ff1e50aff72d02173cfcd7511bd8540b027ee720b75f353f594f834216d0" -dependencies = [ - "parking_lot", -] - -[[package]] -name = "solana-big-mod-exp" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30c80fb6d791b3925d5ec4bf23a7c169ef5090c013059ec3ed7d0b2c04efa085" -dependencies = [ - "num-bigint", - "num-traits", - "solana-define-syscall 3.0.0", -] - -[[package]] -name = "solana-blake3-hasher" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7116e1d942a2432ca3f514625104757ab8a56233787e95144c93950029e31176" -dependencies = [ - "blake3", - "solana-define-syscall 4.0.1", - "solana-hash 4.2.0", -] - -[[package]] -name = "solana-borsh" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc402b16657abbfa9991cd5cbfac5a11d809f7e7d28d3bb291baeb088b39060e" -dependencies = [ - "borsh", -] - -[[package]] -name = "solana-clock" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb62e9381182459a4520b5fe7fb22d423cae736239a6427fc398a88743d0ed59" -dependencies = [ - "serde", - "serde_derive", - "solana-sdk-ids", - "solana-sdk-macro", - "solana-sysvar-id", -] - -[[package]] -name = "solana-cpi" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dea26709d867aada85d0d3617db0944215c8bb28d3745b912de7db13a23280c" -dependencies = [ - "solana-account-info", - "solana-define-syscall 4.0.1", - "solana-instruction", - "solana-program-error", - "solana-pubkey 4.1.0", - "solana-stable-layout", -] - -[[package]] -name = "solana-define-syscall" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9697086a4e102d28a156b8d6b521730335d6951bd39a5e766512bbe09007cee" - -[[package]] -name = "solana-define-syscall" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57e5b1c0bc1d4a4d10c88a4100499d954c09d3fecfae4912c1a074dff68b1738" - -[[package]] -name = "solana-define-syscall" -version = "5.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03aacdd7a61e2109887a7a7f046caebafce97ddf1150f33722eeac04f9039c73" - -[[package]] -name = "solana-epoch-rewards" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b319a4ed70390af911090c020571f0ff1f4ec432522d05ab89f5c08080381995" -dependencies = [ - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-hash 3.1.0", - "solana-sdk-ids", - "solana-sdk-macro", - "solana-sysvar-id", -] - -[[package]] -name = "solana-epoch-schedule" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e5481e72cc4d52c169db73e4c0cd16de8bc943078aac587ec4817a75cc6388f" -dependencies = [ - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk-ids", - "solana-sdk-macro", - "solana-sysvar-id", -] - -[[package]] -name = "solana-epoch-stake" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc6693d0ea833b880514b9b88d95afb80b42762dca98b0712465d1fcbbcb89e" -dependencies = [ - "solana-define-syscall 3.0.0", - "solana-pubkey 3.0.0", -] - -[[package]] -name = "solana-example-mocks" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eb265ff95e28eceda117e2e3d2d2a611ecbbfe911dfeeeecd1521814540ffab" -dependencies = [ - "serde", - "serde_derive", - "solana-hash 4.2.0", - "solana-instruction", - "solana-nonce", - "solana-pubkey 4.1.0", - "solana-sdk-ids", - "solana-system-interface", - "thiserror", -] - -[[package]] -name = "solana-fee-calculator" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b2a5675b2cf8d407c672dc1776492b1f382337720ddf566645ae43237a3d8c3" -dependencies = [ - "log", - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", -] - -[[package]] -name = "solana-frozen-abi" -version = "3.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10284a0ca713bcce197cff2c5408efe06ebe6b6bcc868074d4ccb71b5c60028" -dependencies = [ - "bincode", - "boxcar", - "bs58", - "bv", - "bytes", - "dashmap", - "im", - "log", - "memmap2", - "rand", - "rand_chacha", - "serde", - "serde_derive", - "serde_with", - "sha2", - "solana-frozen-abi-macro", - "thiserror", -] - -[[package]] -name = "solana-frozen-abi-macro" -version = "3.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5396c179ca7d76f866b102eb3819ca3922bdaa33c9ada8005f4e98fd59ab65f5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "solana-hash" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "337c246447142f660f778cf6cb582beba8e28deb05b3b24bfb9ffd7c562e5f41" -dependencies = [ - "solana-hash 4.2.0", -] - -[[package]] -name = "solana-hash" -version = "4.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8064ea1d591ec791be95245058ca40f4f5345d390c200069d0f79bbf55bfae55" -dependencies = [ - "borsh", - "bytemuck", - "bytemuck_derive", - "five8", - "serde", - "serde_derive", - "solana-atomic-u64", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sanitize", - "wincode", -] - -[[package]] -name = "solana-instruction" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee1b699a2c1518028a9982e255e0eca10c44d90006542d9d7f9f40dbce3f7c78" -dependencies = [ - "bincode", - "borsh", - "serde", - "serde_derive", - "solana-define-syscall 4.0.1", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-instruction-error", - "solana-pubkey 4.1.0", -] - -[[package]] -name = "solana-instruction-error" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b04259e03c05faf38a8c24217b5cfe4c90572ae6184ab49cddb1584fdd756d3f" -dependencies = [ - "num-traits", - "solana-program-error", -] - -[[package]] -name = "solana-instructions-sysvar" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ddf67876c541aa1e21ee1acae35c95c6fbc61119814bfef70579317a5e26955" -dependencies = [ - "bitflags", - "qualifier_attr", - "solana-account-info", - "solana-instruction", - "solana-instruction-error", - "solana-program-error", - "solana-pubkey 3.0.0", - "solana-sanitize", - "solana-sdk-ids", - "solana-serialize-utils", - "solana-sysvar-id", -] - -[[package]] -name = "solana-keccak-hasher" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed1c0d16d6fdeba12291a1f068cdf0d479d9bff1141bf44afd7aa9d485f65ef8" -dependencies = [ - "sha3", - "solana-define-syscall 4.0.1", - "solana-hash 4.2.0", -] - -[[package]] -name = "solana-last-restart-slot" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcda154ec827f5fc1e4da0af3417951b7e9b8157540f81f936c4a8b1156134d0" -dependencies = [ - "serde", - "serde_derive", - "solana-sdk-ids", - "solana-sdk-macro", - "solana-sysvar-id", -] - -[[package]] -name = "solana-msg" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "264275c556ea7e22b9d3f87d56305546a38d4eee8ec884f3b126236cb7dcbbb4" -dependencies = [ - "solana-define-syscall 3.0.0", -] - -[[package]] -name = "solana-native-token" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae8dd4c280dca9d046139eb5b7a5ac9ad10403fbd64964c7d7571214950d758f" - -[[package]] -name = "solana-nonce" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc469152a63284ef959b80c59cda015262a021da55d3b8fe42171d89c4b64f8" -dependencies = [ - "solana-fee-calculator", - "solana-hash 4.2.0", - "solana-pubkey 4.1.0", - "solana-sha256-hasher", -] - -[[package]] -name = "solana-program" -version = "4.0.0" -dependencies = [ - "memoffset", - "solana-account-info", - "solana-big-mod-exp", - "solana-blake3-hasher", - "solana-borsh", - "solana-clock", - "solana-cpi", - "solana-define-syscall 5.0.0", - "solana-epoch-rewards", - "solana-epoch-schedule", - "solana-epoch-stake", - "solana-example-mocks", - "solana-fee-calculator", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-hash 4.2.0", - "solana-instruction", - "solana-instruction-error", - "solana-instructions-sysvar", - "solana-keccak-hasher", - "solana-last-restart-slot", - "solana-msg", - "solana-native-token", - "solana-program-entrypoint", - "solana-program-error", - "solana-program-memory", - "solana-program-option", - "solana-program-pack", - "solana-pubkey 4.1.0", - "solana-rent", - "solana-sdk-ids", - "solana-secp256k1-recover", - "solana-serde-varint", - "solana-serialize-utils", - "solana-sha256-hasher", - "solana-short-vec", - "solana-slot-hashes", - "solana-slot-history", - "solana-stable-layout", - "solana-system-interface", - "solana-sysvar", - "solana-sysvar-id", -] - -[[package]] -name = "solana-program-entrypoint" -version = "3.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c9b0a1ff494e05f503a08b3d51150b73aa639544631e510279d6375f290997" -dependencies = [ - "solana-account-info", - "solana-define-syscall 4.0.1", - "solana-program-error", - "solana-pubkey 4.1.0", -] - -[[package]] -name = "solana-program-error" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1af32c995a7b692a915bb7414d5f8e838450cf7c70414e763d8abcae7b51f28" -dependencies = [ - "borsh", - "serde", - "serde_derive", -] - -[[package]] -name = "solana-program-memory" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4068648649653c2c50546e9a7fb761791b5ab0cda054c771bb5808d3a4b9eb52" -dependencies = [ - "solana-define-syscall 4.0.1", -] - -[[package]] -name = "solana-program-option" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e7b4ddb464f274deb4a497712664c3b612e3f5f82471d4e47710fc4ab1c3095" - -[[package]] -name = "solana-program-pack" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c169359de21f6034a63ebf96d6b380980307df17a8d371344ff04a883ec4e9d0" -dependencies = [ - "solana-program-error", -] - -[[package]] -name = "solana-pubkey" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8909d399deb0851aa524420beeb5646b115fd253ef446e35fe4504c904da3941" -dependencies = [ - "solana-address 1.1.0", -] - -[[package]] -name = "solana-pubkey" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b06bd918d60111ee1f97de817113e2040ca0cedb740099ee8d646233f6b906c" -dependencies = [ - "solana-address 2.2.0", -] - -[[package]] -name = "solana-rent" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763fe5c88a76ce18235db595b21d38b7aebf6db56b324cdf9fc96059f4410823" -dependencies = [ - "serde", - "serde_derive", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-sdk-ids", - "solana-sdk-macro", - "solana-sysvar-id", -] - -[[package]] -name = "solana-sanitize" -version = "3.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf09694a0fc14e5ffb18f9b7b7c0f15ecb6eac5b5610bf76a1853459d19daf9" - -[[package]] -name = "solana-sdk-ids" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def234c1956ff616d46c9dd953f251fa7096ddbaa6d52b165218de97882b7280" -dependencies = [ - "solana-address 2.2.0", -] - -[[package]] -name = "solana-sdk-macro" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6430000e97083460b71d9fbadc52a2ab2f88f53b3a4c5e58c5ae3640a0e8c00" -dependencies = [ - "bs58", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "solana-secp256k1-recover" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de18cfdab99eeb940fbedd8c981fa130c0d76252da75d05446f22fae8b51932" -dependencies = [ - "k256", - "solana-define-syscall 4.0.1", - "thiserror", -] - -[[package]] -name = "solana-serde-varint" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e5174c57d5ff3c1995f274d17156964664566e2cde18a07bba1586d35a70d3b" -dependencies = [ - "serde", -] - -[[package]] -name = "solana-serialize-utils" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e41dd8feea239516c623a02f0a81c2367f4b604d7965237fed0751aeec33ed" -dependencies = [ - "solana-instruction-error", - "solana-pubkey 3.0.0", - "solana-sanitize", -] - -[[package]] -name = "solana-sha256-hasher" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db7dc3011ea4c0334aaaa7e7128cb390ecf546b28d412e9bf2064680f57f588f" -dependencies = [ - "sha2", - "solana-define-syscall 4.0.1", - "solana-hash 4.2.0", -] - -[[package]] -name = "solana-short-vec" -version = "3.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3bd991c2cc415291c86bb0b6b4d53e93d13bb40344e4c5a2884e0e4f5fa93f" -dependencies = [ - "solana-frozen-abi", - "solana-frozen-abi-macro", -] - -[[package]] -name = "solana-slot-hashes" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80a293f952293281443c04f4d96afd9d547721923d596e92b4377ed2360f1746" -dependencies = [ - "serde", - "serde_derive", - "solana-hash 3.1.0", - "solana-sdk-ids", - "solana-sysvar-id", -] - -[[package]] -name = "solana-slot-history" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f914f6b108f5bba14a280b458d023e3621c9973f27f015a4d755b50e88d89e97" -dependencies = [ - "bv", - "serde", - "serde_derive", - "solana-sdk-ids", - "solana-sysvar-id", -] - -[[package]] -name = "solana-stable-layout" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1da74507795b6e8fb60b7c7306c0c36e2c315805d16eaaf479452661234685ac" -dependencies = [ - "solana-instruction", - "solana-pubkey 3.0.0", -] - -[[package]] -name = "solana-system-interface" -version = "3.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14591d6508042ebefb110305d3ba761615927146a26917ade45dc332d8e1ecde" -dependencies = [ - "num-traits", - "serde", - "serde_derive", - "solana-address 2.2.0", - "solana-instruction", - "solana-msg", - "solana-program-error", -] - -[[package]] -name = "solana-sysvar" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1632b69b4f72489db5949a10e8308c229dfa003f99ecaa7477b376807c7b81f4" -dependencies = [ - "base64", - "bincode", - "bytemuck", - "bytemuck_derive", - "lazy_static", - "serde", - "serde_derive", - "solana-account-info", - "solana-clock", - "solana-define-syscall 5.0.0", - "solana-epoch-rewards", - "solana-epoch-schedule", - "solana-fee-calculator", - "solana-frozen-abi", - "solana-frozen-abi-macro", - "solana-hash 4.2.0", - "solana-instruction", - "solana-last-restart-slot", - "solana-program-entrypoint", - "solana-program-error", - "solana-program-memory", - "solana-pubkey 4.1.0", - "solana-rent", - "solana-sdk-ids", - "solana-sdk-macro", - "solana-slot-hashes", - "solana-slot-history", - "solana-sysvar-id", -] - -[[package]] -name = "solana-sysvar-id" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17358d1e9a13e5b9c2264d301102126cf11a47fd394cdf3dec174fe7bc96e1de" -dependencies = [ - "solana-address 2.2.0", - "solana-sdk-ids", -] - -[[package]] -name = "spki" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" -dependencies = [ - "base64ct", - "der", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "2.0.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinyvec" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022db8904dfa342efe721985167e9fcd16c29b226db4397ed752a761cfce81e8" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "toml_datetime" -version = "0.6.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" - -[[package]] -name = "toml_edit" -version = "0.22.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" -dependencies = [ - "indexmap", - "toml_datetime", - "toml_write", - "winnow", -] - -[[package]] -name = "toml_write" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "unicode-ident" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasip2" -version = "1.0.1+wasi-0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" -dependencies = [ - "wit-bindgen", -] - -[[package]] -name = "wincode" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "466e67917609b2d40a838a5b972d1a6237c9749600cb8de8f65559b90d48485b" -dependencies = [ - "pastey", - "proc-macro2", - "quote", - "thiserror", - "wincode-derive", -] - -[[package]] -name = "wincode-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a7a568eda854acc9945ed136a9d50b8c6d31911584624958808ae96eee3912" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[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.7.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec" -dependencies = [ - "memchr", -] - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" - -[[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", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/sp4/solana-program-4.0.0/Cargo.toml b/sp4/solana-program-4.0.0/Cargo.toml deleted file mode 100644 index fc626fb6..00000000 --- a/sp4/solana-program-4.0.0/Cargo.toml +++ /dev/null @@ -1,287 +0,0 @@ -# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO -# -# When uploading crates to the registry Cargo will automatically -# "normalize" Cargo.toml files for maximal compatibility -# with all versions of Cargo and also rewrite `path` dependencies -# to registry (e.g., crates.io) dependencies. -# -# If you are reading this file be aware that the original Cargo.toml -# will likely look very different (and much more reasonable). -# See Cargo.toml.orig for the original contents. - -[package] -edition = "2021" -rust-version = "1.81.0" -name = "solana-program" -version = "4.0.0" -authors = ["Anza Maintainers "] -build = false -include = [ - "src/**/*", - "README.md", -] -autolib = false -autobins = false -autoexamples = false -autotests = false -autobenches = false -description = "Solana Program" -homepage = "https://anza.xyz/" -documentation = "https://docs.rs/solana-program" -readme = "README.md" -license = "Apache-2.0" -repository = "https://github.com/anza-xyz/solana-sdk" - -[package.metadata.docs.rs] -targets = [ - "x86_64-unknown-linux-gnu", - "wasm32-unknown-unknown", -] -all-features = true -rustdoc-args = ["--cfg=docsrs"] - -[features] -borsh = [ - "dep:solana-borsh", - "solana-hash/borsh", - "solana-instruction/borsh", - "solana-program-error/borsh", - "solana-pubkey/borsh", -] -default = ["borsh"] -dev-context-only-utils = [ - "solana-instructions-sysvar/dev-context-only-utils", - "dep:solana-system-interface", - "solana-system-interface/bincode", -] -frozen-abi = [ - "dep:solana-frozen-abi", - "dep:solana-frozen-abi-macro", - "solana-epoch-rewards/frozen-abi", - "solana-epoch-schedule/frozen-abi", - "solana-fee-calculator/frozen-abi", - "solana-hash/frozen-abi", - "solana-instruction/frozen-abi", - "solana-pubkey/frozen-abi", - "solana-rent/frozen-abi", - "solana-short-vec/frozen-abi", - "solana-sysvar/frozen-abi", -] - -[lib] -name = "solana_program" -crate-type = ["rlib"] -path = "src/lib.rs" - -[dependencies.memoffset] -version = "0.9" - -[dependencies.solana-account-info] -version = "3.0.0" -features = ["bincode"] - -[dependencies.solana-big-mod-exp] -version = "3.0.0" - -[dependencies.solana-blake3-hasher] -version = "3.0.0" -features = ["blake3"] - -[dependencies.solana-borsh] -version = "3.0.0" -optional = true - -[dependencies.solana-clock] -version = "3.0.0" -features = [ - "serde", - "sysvar", -] - -[dependencies.solana-cpi] -version = "3.0.0" - -[dependencies.solana-epoch-rewards] -version = "3.0.0" -features = [ - "serde", - "sysvar", -] - -[dependencies.solana-epoch-schedule] -version = "3.0.0" -features = [ - "serde", - "sysvar", -] - -[dependencies.solana-epoch-stake] -version = "3.0.0" - -[dependencies.solana-fee-calculator] -version = "3.1.0" -features = ["serde"] - -[dependencies.solana-frozen-abi] -version = "3.0.0" -features = ["frozen-abi"] -optional = true - -[dependencies.solana-frozen-abi-macro] -version = "3.0.1" -features = ["frozen-abi"] -optional = true - -[dependencies.solana-hash] -version = "4.2.0" -features = [ - "bytemuck", - "serde", - "std", -] -default-features = false - -[dependencies.solana-instruction] -version = "3.0.0" -features = [ - "bincode", - "serde", - "std", - "syscalls", -] -default-features = false - -[dependencies.solana-instruction-error] -version = "2.0.0" -features = ["num-traits"] - -[dependencies.solana-instructions-sysvar] -version = "3.0.0" - -[dependencies.solana-keccak-hasher] -version = "3.0.0" -features = ["sha3"] - -[dependencies.solana-last-restart-slot] -version = "3.0.0" -features = [ - "serde", - "sysvar", -] - -[dependencies.solana-msg] -version = "3.0.0" -features = ["std"] -default-features = false - -[dependencies.solana-native-token] -version = "3.0.0" - -[dependencies.solana-program-entrypoint] -version = "3.0.0" - -[dependencies.solana-program-error] -version = "3.0.0" -features = ["serde"] - -[dependencies.solana-program-memory] -version = "3.0.0" - -[dependencies.solana-program-option] -version = "3.0.0" - -[dependencies.solana-program-pack] -version = "3.0.0" - -[dependencies.solana-pubkey] -version = "4.1.0" -features = [ - "bytemuck", - "curve25519", - "serde", - "std", -] -default-features = false - -[dependencies.solana-rent] -version = "4.0.0" -features = [ - "serde", - "sysvar", -] -default-features = false - -[dependencies.solana-sdk-ids] -version = "3.0.0" - -[dependencies.solana-secp256k1-recover] -version = "3.0.0" - -[dependencies.solana-serde-varint] -version = "3.0.0" - -[dependencies.solana-serialize-utils] -version = "3.1.0" - -[dependencies.solana-sha256-hasher] -version = "3.0.0" -features = ["sha2"] - -[dependencies.solana-short-vec] -version = "3.2.0" -default-features = false - -[dependencies.solana-slot-hashes] -version = "3.0.0" -features = [ - "serde", - "sysvar", -] - -[dependencies.solana-slot-history] -version = "3.0.0" -features = [ - "serde", - "sysvar", -] - -[dependencies.solana-stable-layout] -version = "3.0.0" - -[dependencies.solana-system-interface] -version = "3.0" -features = ["bincode"] -optional = true - -[dependencies.solana-sysvar] -version = "4.0.0" -features = [ - "bincode", - "bytemuck", -] - -[dependencies.solana-sysvar-id] -version = "3.0.0" - -[dev-dependencies.solana-pubkey] -version = "4.1.0" -features = ["dev-context-only-utils"] -default-features = false - -[dev-dependencies.solana-sysvar] -version = "4.0.0" -features = ["dev-context-only-utils"] - -[target.'cfg(not(target_os = "solana"))'.dependencies.solana-example-mocks] -version = "4.0.0" - -[target.'cfg(target_os = "solana")'.dependencies.solana-define-syscall] -version = "5.0.0" - -[lints.rust.unexpected_cfgs] -level = "warn" -priority = 0 -check-cfg = [ - 'cfg(target_os, values("solana"))', - 'cfg(feature, values("frozen-abi", "no-entrypoint"))', -] diff --git a/sp4/solana-program-4.0.0/Cargo.toml.orig b/sp4/solana-program-4.0.0/Cargo.toml.orig deleted file mode 100644 index d0283e4a..00000000 --- a/sp4/solana-program-4.0.0/Cargo.toml.orig +++ /dev/null @@ -1,113 +0,0 @@ -[package] -name = "solana-program" -description = "Solana Program" -documentation = "https://docs.rs/solana-program" -readme = "README.md" -version = "4.0.0" -authors = { workspace = true } -repository = { workspace = true } -homepage = { workspace = true } -license = { workspace = true } -edition = { workspace = true } -rust-version = "1.81.0" # solana platform-tools rust version -include = ["src/**/*", "README.md"] - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] -all-features = true -rustdoc-args = ["--cfg=docsrs"] - -[lib] -crate-type = ["rlib"] - -[features] -default = ["borsh"] -borsh = [ - "dep:solana-borsh", - "solana-hash/borsh", - "solana-instruction/borsh", - "solana-program-error/borsh", - "solana-pubkey/borsh", -] -dev-context-only-utils = [ - "solana-instructions-sysvar/dev-context-only-utils", - "dep:solana-system-interface", - "solana-system-interface/bincode", -] -frozen-abi = [ - "dep:solana-frozen-abi", - "dep:solana-frozen-abi-macro", - "solana-epoch-rewards/frozen-abi", - "solana-epoch-schedule/frozen-abi", - "solana-fee-calculator/frozen-abi", - "solana-hash/frozen-abi", - "solana-instruction/frozen-abi", - "solana-pubkey/frozen-abi", - "solana-rent/frozen-abi", - "solana-short-vec/frozen-abi", - "solana-sysvar/frozen-abi", -] - -[dependencies] -memoffset = { workspace = true } -solana-account-info = { workspace = true, features = ["bincode"] } -solana-big-mod-exp = { workspace = true } -solana-blake3-hasher = { workspace = true, features = ["blake3"] } -solana-borsh = { workspace = true, optional = true } -solana-clock = { workspace = true, features = ["serde", "sysvar"] } -solana-cpi = { workspace = true } -solana-epoch-rewards = { workspace = true, features = ["serde", "sysvar"] } -solana-epoch-schedule = { workspace = true, features = ["serde", "sysvar"] } -solana-epoch-stake = { workspace = true } -solana-fee-calculator = { workspace = true, features = ["serde"] } -solana-frozen-abi = { workspace = true, optional = true, features = ["frozen-abi"] } -solana-frozen-abi-macro = { workspace = true, optional = true, features = ["frozen-abi"] } -solana-hash = { workspace = true, features = [ - "bytemuck", - "serde", - "std", -] } -solana-instruction = { workspace = true, default-features = false, features = [ - "bincode", - "serde", - "std", - "syscalls", -] } -solana-instruction-error = { workspace = true, features = ["num-traits"] } -solana-instructions-sysvar = { workspace = true } -solana-keccak-hasher = { workspace = true, features = ["sha3"] } -solana-last-restart-slot = { workspace = true, features = ["serde", "sysvar"] } -solana-msg = { workspace = true, features = ["std"] } -solana-native-token = { workspace = true } -solana-program-entrypoint = { workspace = true } -solana-program-error = { workspace = true, features = ["serde"] } -solana-program-memory = { workspace = true } -solana-program-option = { workspace = true } -solana-program-pack = { workspace = true } -solana-pubkey = { workspace = true, features = ["bytemuck", "curve25519", "serde", "std"] } -solana-rent = { workspace = true, features = ["serde", "sysvar"] } -solana-sdk-ids = { workspace = true } -solana-secp256k1-recover = { workspace = true } -solana-serde-varint = { workspace = true } -solana-serialize-utils = { workspace = true } -solana-sha256-hasher = { workspace = true, features = ["sha2"] } -solana-short-vec = { workspace = true } -solana-slot-hashes = { workspace = true, features = ["serde", "sysvar"] } -solana-slot-history = { workspace = true, features = ["serde", "sysvar"] } -solana-stable-layout = { workspace = true } -solana-system-interface = { optional = true, workspace = true, features = ["bincode"] } -solana-sysvar = { workspace = true, features = ["bincode", "bytemuck"] } -solana-sysvar-id = { workspace = true } - -[target.'cfg(not(target_os = "solana"))'.dependencies] -solana-example-mocks = { workspace = true } - -[target.'cfg(target_os = "solana")'.dependencies] -solana-define-syscall = { workspace = true } - -[dev-dependencies] -solana-pubkey = { workspace = true, features = ["dev-context-only-utils"] } -solana-sysvar = { workspace = true, features = ["dev-context-only-utils"] } - -[lints] -workspace = true diff --git a/sp4/solana-program-4.0.0/README.md b/sp4/solana-program-4.0.0/README.md deleted file mode 100644 index c93d4a14..00000000 --- a/sp4/solana-program-4.0.0/README.md +++ /dev/null @@ -1,15 +0,0 @@ -

- - Solana - -

- -# Solana Program - -Use the Solana Program Crate to write on-chain programs in Rust. If writing client-side applications, use the [Solana SDK Crate](https://crates.io/crates/solana-sdk) instead. - -More information about Solana is available in the [Solana documentation](https://solana.com/docs). - -[Solana Program Library](https://github.com/solana-labs/solana-program-library) provides examples of how to use this crate. - -Still have questions? Ask us on [Stack Exchange](https://sola.na/sse) diff --git a/sp4/solana-program-4.0.0/src/bpf_loader.rs b/sp4/solana-program-4.0.0/src/bpf_loader.rs deleted file mode 100644 index 8d3177b1..00000000 --- a/sp4/solana-program-4.0.0/src/bpf_loader.rs +++ /dev/null @@ -1,23 +0,0 @@ -//! The latest BPF loader native program. -//! -//! The BPF loader is responsible for loading, finalizing, and executing BPF -//! programs. Not all networks may support the latest loader. You can use the -//! command-line tools to check if this version of the loader is supported by -//! requesting the account info for the public key below. -//! -//! The program format may change between loaders, and it is crucial to build -//! your program against the proper entrypoint semantics. All programs being -//! deployed to this BPF loader must build against the latest entrypoint version -//! located in `entrypoint.rs`. -//! -//! Note: Programs built for older loaders must use a matching entrypoint -//! version. An example is [`bpf_loader_deprecated`] which requires -//! [`entrypoint_deprecated`]. -//! -//! The `solana program deploy` CLI command uses the -//! [upgradeable BPF loader][ubpfl]. -//! -//! [`bpf_loader_deprecated`]: crate::bpf_loader_deprecated -//! [`entrypoint_deprecated`]: mod@crate::entrypoint_deprecated -//! [ubpfl]: https://docs.rs/solana-loader-v3-interface/latest/solana_loader_v3_interface/index.html -pub use solana_sdk_ids::bpf_loader::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/bpf_loader_deprecated.rs b/sp4/solana-program-4.0.0/src/bpf_loader_deprecated.rs deleted file mode 100644 index 8662a5ec..00000000 --- a/sp4/solana-program-4.0.0/src/bpf_loader_deprecated.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! The original and now deprecated Solana BPF loader. -//! -//! The BPF loader is responsible for loading, finalizing, and executing BPF -//! programs. -//! -//! This loader is deprecated, and it is strongly encouraged to build for and -//! deploy to the latest BPF loader. For more information see `bpf_loader.rs` -//! -//! The program format may change between loaders, and it is crucial to build -//! your program against the proper entrypoint semantics. All programs being -//! deployed to this BPF loader must build against the deprecated entrypoint -//! version located in `entrypoint_deprecated.rs`. -pub use solana_sdk_ids::bpf_loader_deprecated::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/compute_units.rs b/sp4/solana-program-4.0.0/src/compute_units.rs deleted file mode 100644 index 6b7f2712..00000000 --- a/sp4/solana-program-4.0.0/src/compute_units.rs +++ /dev/null @@ -1,13 +0,0 @@ -/// Return the remaining compute units the program may consume -#[inline] -pub fn sol_remaining_compute_units() -> u64 { - #[cfg(target_os = "solana")] - unsafe { - crate::syscalls::sol_remaining_compute_units() - } - - #[cfg(not(target_os = "solana"))] - { - crate::program_stubs::sol_remaining_compute_units() - } -} diff --git a/sp4/solana-program-4.0.0/src/ed25519_program.rs b/sp4/solana-program-4.0.0/src/ed25519_program.rs deleted file mode 100644 index a41385ad..00000000 --- a/sp4/solana-program-4.0.0/src/ed25519_program.rs +++ /dev/null @@ -1,4 +0,0 @@ -//! The [ed25519 native program][np]. -//! -//! [np]: https://docs.solanalabs.com/runtime/programs#ed25519-program -pub use solana_sdk_ids::ed25519_program::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/entrypoint_deprecated.rs b/sp4/solana-program-4.0.0/src/entrypoint_deprecated.rs deleted file mode 100644 index 60fea304..00000000 --- a/sp4/solana-program-4.0.0/src/entrypoint_deprecated.rs +++ /dev/null @@ -1,139 +0,0 @@ -//! The Rust-based BPF program entrypoint supported by the original BPF loader. -//! -//! The original BPF loader is deprecated and exists for backwards-compatibility -//! reasons. This module should not be used by new programs. -//! -//! For more information see the [`bpf_loader_deprecated`] module. -//! -//! [`bpf_loader_deprecated`]: crate::bpf_loader_deprecated - -#![allow(clippy::arithmetic_side_effects)] - -extern crate alloc; -use { - crate::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey}, - alloc::vec::Vec, - std::{ - mem::size_of, - result::Result as ResultGeneric, - slice::{from_raw_parts, from_raw_parts_mut}, - }, -}; - -pub type ProgramResult = ResultGeneric<(), ProgramError>; - -/// User implemented function to process an instruction -/// -/// program_id: Program ID of the currently executing program -/// accounts: Accounts passed as part of the instruction -/// instruction_data: Instruction data -pub type ProcessInstruction = - fn(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult; - -/// Programs indicate success with a return value of 0 -pub const SUCCESS: u64 = 0; - -/// Declare the program entrypoint. -/// -/// Deserialize the program input arguments and call -/// the user defined `process_instruction` function. -/// Users must call this macro otherwise an entrypoint for -/// their program will not be created. -#[macro_export] -macro_rules! entrypoint_deprecated { - ($process_instruction:ident) => { - /// # Safety - #[no_mangle] - pub unsafe extern "C" fn entrypoint(input: *mut u8) -> u64 { - let (program_id, accounts, instruction_data) = - unsafe { $crate::entrypoint_deprecated::deserialize(input) }; - match $process_instruction(&program_id, &accounts, &instruction_data) { - Ok(()) => $crate::entrypoint_deprecated::SUCCESS, - Err(error) => error.into(), - } - } - }; -} - -/// Deserialize the input arguments -/// -/// # Safety -#[allow(clippy::type_complexity)] -pub unsafe fn deserialize<'a>(input: *mut u8) -> (&'a Pubkey, Vec>, &'a [u8]) { - let mut offset: usize = 0; - - // Number of accounts present - - #[allow(clippy::cast_ptr_alignment)] - let num_accounts = *(input.add(offset) as *const u64) as usize; - offset += size_of::(); - - // Account Infos - - let mut accounts = Vec::with_capacity(num_accounts); - for _ in 0..num_accounts { - let dup_info = *(input.add(offset) as *const u8); - offset += size_of::(); - if dup_info == u8::MAX { - #[allow(clippy::cast_ptr_alignment)] - let is_signer = *(input.add(offset) as *const u8) != 0; - offset += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let is_writable = *(input.add(offset) as *const u8) != 0; - offset += size_of::(); - - let key: &Pubkey = &*(input.add(offset) as *const Pubkey); - offset += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let lamports = &mut *(input.add(offset) as *mut u64); - offset += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let data_len = *(input.add(offset) as *const u64) as usize; - offset += size_of::(); - - let data = from_raw_parts_mut(input.add(offset), data_len); - offset += data_len; - - let owner: &Pubkey = &*(input.add(offset) as *const Pubkey); - offset += size_of::(); - - #[allow(clippy::cast_ptr_alignment)] - let executable = *(input.add(offset) as *const u8) != 0; - offset += size_of::(); - - // rent epoch is skipped - offset += size_of::(); - - accounts.push(AccountInfo::new( - key, - is_signer, - is_writable, - lamports, - data, - owner, - executable, - )); - } else { - // Duplicate account, clone the original - accounts.push(accounts[dup_info as usize].clone()); - } - } - - // Instruction data - - #[allow(clippy::cast_ptr_alignment)] - let instruction_data_len = *(input.add(offset) as *const u64) as usize; - offset += size_of::(); - - let instruction_data = { from_raw_parts(input.add(offset), instruction_data_len) }; - offset += instruction_data_len; - - // Program Id - - let program_id: &Pubkey = &*(input.add(offset) as *const Pubkey); - - (program_id, accounts, instruction_data) -} diff --git a/sp4/solana-program-4.0.0/src/epoch_schedule.rs b/sp4/solana-program-4.0.0/src/epoch_schedule.rs deleted file mode 100644 index 56addac3..00000000 --- a/sp4/solana-program-4.0.0/src/epoch_schedule.rs +++ /dev/null @@ -1,8 +0,0 @@ -#[deprecated( - since = "2.1.0", - note = "Use solana-clock and solana-epoch-schedule crates instead." -)] -pub use { - solana_clock::{Epoch, Slot, DEFAULT_SLOTS_PER_EPOCH}, - solana_epoch_schedule::*, -}; diff --git a/sp4/solana-program-4.0.0/src/hash.rs b/sp4/solana-program-4.0.0/src/hash.rs deleted file mode 100644 index ffdfa0d1..00000000 --- a/sp4/solana-program-4.0.0/src/hash.rs +++ /dev/null @@ -1,11 +0,0 @@ -//! Hashing with the [SHA-256] hash function, and a general [`Hash`] type. -//! -//! [SHA-256]: https://en.wikipedia.org/wiki/SHA-2 -//! [`Hash`]: struct@Hash - -#[cfg(not(target_os = "solana"))] -pub use solana_sha256_hasher::Hasher; -pub use { - solana_hash::{Hash, ParseHashError, HASH_BYTES}, - solana_sha256_hasher::{hash, hashv}, -}; diff --git a/sp4/solana-program-4.0.0/src/incinerator.rs b/sp4/solana-program-4.0.0/src/incinerator.rs deleted file mode 100644 index 82b54e6b..00000000 --- a/sp4/solana-program-4.0.0/src/incinerator.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[deprecated(since = "2.2.0", note = "Use `solana_sdk_ids::incinerator` instead")] -pub use solana_sdk_ids::incinerator::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/instruction.rs b/sp4/solana-program-4.0.0/src/instruction.rs deleted file mode 100644 index 38fb5131..00000000 --- a/sp4/solana-program-4.0.0/src/instruction.rs +++ /dev/null @@ -1,52 +0,0 @@ -pub use solana_instruction::{ - error::InstructionError, AccountMeta, Instruction, ProcessedSiblingInstruction, - TRANSACTION_LEVEL_STACK_HEIGHT, -}; - -/// Returns a sibling instruction from the processed sibling instruction list. -/// -/// The processed sibling instruction list is a reverse-ordered list of -/// successfully processed sibling instructions. For example, given the call flow: -/// -/// A -/// B -> C -> D -/// B -> E -/// B -> F -/// -/// Then B's processed sibling instruction list is: `[A]` -/// Then F's processed sibling instruction list is: `[E, C]` -pub fn get_processed_sibling_instruction(index: usize) -> Option { - #[cfg(target_os = "solana")] - { - solana_instruction::syscalls::get_processed_sibling_instruction(index) - } - - #[cfg(not(target_os = "solana"))] - { - crate::program_stubs::sol_get_processed_sibling_instruction(index) - } -} - -/// Get the current stack height, transaction-level instructions are height -/// TRANSACTION_LEVEL_STACK_HEIGHT, fist invoked inner instruction is height -/// TRANSACTION_LEVEL_STACK_HEIGHT + 1, etc... -pub fn get_stack_height() -> usize { - #[cfg(target_os = "solana")] - { - solana_instruction::syscalls::get_stack_height() - } - - #[cfg(not(target_os = "solana"))] - { - crate::program_stubs::sol_get_stack_height() as usize - } -} - -// TODO: remove this. -/// Addition that returns [`InstructionError::InsufficientFunds`] on overflow. -/// -/// This is an internal utility function. -#[doc(hidden)] -pub fn checked_add(a: u64, b: u64) -> Result { - a.checked_add(b).ok_or(InstructionError::InsufficientFunds) -} diff --git a/sp4/solana-program-4.0.0/src/lamports.rs b/sp4/solana-program-4.0.0/src/lamports.rs deleted file mode 100644 index 87cc2b6d..00000000 --- a/sp4/solana-program-4.0.0/src/lamports.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! Re-exports the [`LamportsError`] type for backwards compatibility. -#[deprecated( - since = "2.1.0", - note = "Use solana_instruction_error::LamportsError instead" -)] -pub use solana_instruction_error::LamportsError; diff --git a/sp4/solana-program-4.0.0/src/lib.rs b/sp4/solana-program-4.0.0/src/lib.rs deleted file mode 100644 index 927a2d2d..00000000 --- a/sp4/solana-program-4.0.0/src/lib.rs +++ /dev/null @@ -1,690 +0,0 @@ -//! The base library for all Solana on-chain Rust programs. -//! -//! All Solana Rust programs that run on-chain will link to this crate, which -//! acts as a standard library for Solana programs. Solana programs also link to -//! the [Rust standard library][std], though it is [modified][sstd] for the -//! Solana runtime environment. While off-chain programs that interact with the -//! Solana network _can_ link to this crate, they typically instead use the -//! [`solana-sdk`] crate, which reexports all modules from `solana-program`. -//! -//! [std]: https://doc.rust-lang.org/stable/std/ -//! [sstd]: https://solana.com/docs/programs/limitations#rust-libraries -//! [`solana-sdk`]: https://docs.rs/solana-sdk/latest/solana_sdk/ -//! -//! This library defines -//! -//! - macros for declaring the [program entrypoint][pe], -//! - [core data types][cdt], -//! - [logging] macros, -//! - [serialization] methods, -//! - methods for [cross-program instruction execution][cpi], -//! - program IDs and instruction constructors for the system program and other -//! [native programs][np], -//! - [sysvar] accessors. -//! -//! [pe]: #defining-a-solana-program -//! [cdt]: #core-data-types -//! [logging]: crate::log -//! [serialization]: #serialization -//! [np]: #native-programs -//! [cpi]: #cross-program-instruction-execution -//! [sysvar]: crate::sysvar -//! -//! Idiomatic examples of `solana-program` usage can be found in -//! [the Solana Program Library][spl]. -//! -//! [spl]: https://github.com/solana-labs/solana-program-library -//! -//! # Defining a solana program -//! -//! Solana program crates have some unique properties compared to typical Rust -//! programs: -//! -//! - They are often compiled for both on-chain use and off-chain use. This is -//! primarily because off-chain clients may need access to data types -//! defined by the on-chain program. -//! - They do not define a `main` function, but instead define their entrypoint -//! with the [`entrypoint!`] macro. -//! - They are compiled as the ["cdylib"] crate type for dynamic loading -//! by the Solana runtime. -//! - They run in a constrained VM environment, and while they do have access to -//! the [Rust standard library][std], many features of the standard library, -//! particularly related to OS services, will fail at runtime, will silently -//! do nothing, or are not defined. See the [restrictions to the Rust standard -//! library][sstd] in the Solana documentation for more. -//! -//! [std]: https://doc.rust-lang.org/std/index.html -//! ["cdylib"]: https://doc.rust-lang.org/reference/linkage.html -//! -//! Because multiple crates that are linked together cannot all define -//! program entrypoints (see the [`entrypoint!`] documentation) a common -//! convention is to use a [Cargo feature] called `no-entrypoint` to allow -//! the program entrypoint to be disabled. -//! -//! [Cargo feature]: https://doc.rust-lang.org/cargo/reference/features.html -//! -//! The skeleton of a Solana program typically looks like: -//! -//! ``` -//! #[cfg(not(feature = "no-entrypoint"))] -//! pub mod entrypoint { -//! use solana_program::{ -//! account_info::AccountInfo, -//! entrypoint, -//! entrypoint::ProgramResult, -//! pubkey::Pubkey, -//! }; -//! -//! entrypoint!(process_instruction); -//! -//! pub fn process_instruction( -//! program_id: &Pubkey, -//! accounts: &[AccountInfo], -//! instruction_data: &[u8], -//! ) -> ProgramResult { -//! // Decode and dispatch instructions here. -//! todo!() -//! } -//! } -//! -//! // Additional code goes here. -//! ``` -//! -//! With a `Cargo.toml` file that contains -//! -//! ```toml -//! [lib] -//! crate-type = ["cdylib"] -//! -//! [features] -//! no-entrypoint = [] -//! ``` -//! -//! Note that a Solana program must specify its crate-type as "cdylib", to -//! be discovered and built by the `cargo-build-sbf` command as a deployable program. -//! Solana programs also often have crate-type "rlib" so they can be linked to other Rust crates. -//! Avoid using "rlib" and "cdylib" crates together, since their combined usage precludes -//! compiler optimizations that may decrease program size and CU usage. -//! -//! Prefer writing a separate package if it is supposed to be used as a library for other Solana -//! programs (i.e. a "rlib" only crate). This would be normally the case for defining account -//! types and helpers that are used by both clients and program. When creating a Rust project -//! intended to be a program ready for deployment, use only the "cdylib" crate type. -//! -//! # On-chain vs. off-chain compilation targets -//! -//! Solana programs run on the [rbpf] VM, which implements a variant of the -//! [eBPF] instruction set. Because this crate can be compiled for both on-chain -//! and off-chain execution, the environments of which are significantly -//! different, it extensively uses [conditional compilation][cc] to tailor its -//! implementation to the environment. The `cfg` predicate used for identifying -//! compilation for on-chain programs is `target_os = "solana"`, as in this -//! example from the `solana-program` codebase that logs a message via a -//! syscall when run on-chain, and via a library call when offchain: -//! -//! [rbpf]: https://github.com/solana-labs/rbpf -//! [eBPF]: https://ebpf.io/ -//! [cc]: https://doc.rust-lang.org/reference/conditional-compilation.html -//! -//! ``` -//! pub fn sol_log(message: &str) { -//! #[cfg(target_os = "solana")] -//! unsafe { -//! sol_log_(message.as_ptr(), message.len() as u64); -//! } -//! -//! #[cfg(not(target_os = "solana"))] -//! program_stubs::sol_log(message); -//! } -//! # mod program_stubs { -//! # pub(crate) fn sol_log(message: &str) { } -//! # } -//! ``` -//! -//! This `cfg` pattern is suitable as well for user code that needs to work both -//! on-chain and off-chain. -//! -//! `solana-program` and `solana-sdk` were previously a single crate. Because of -//! this history, and because of the dual-usage of `solana-program` for two -//! different environments, it contains some features that are not available to -//! on-chain programs at compile-time. It also contains some on-chain features -//! that will fail in off-chain scenarios at runtime. This distinction is not -//! well-reflected in the documentation. -//! -//! For a more complete description of Solana's implementation of eBPF and its -//! limitations, see the main Solana documentation for [on-chain programs][ocp]. -//! -//! [ocp]: https://solana.com/docs/programs -//! -//! # Core data types -//! -//! - [`Pubkey`] — The address of a [Solana account][acc]. Some account -//! addresses are [ed25519] public keys, with corresponding secret keys that -//! are managed off-chain. Often, though, account addresses do not have -//! corresponding secret keys — as with [_program derived -//! addresses_][pdas] — or the secret key is not relevant to the -//! operation of a program, and may have even been disposed of. As running -//! Solana programs can not safely create or manage secret keys, the full -//! [`Keypair`] is not defined in `solana-program` but in `solana-sdk`. -//! - [`Hash`] — A cryptographic hash. Used to uniquely identify blocks, -//! and also for general purpose hashing. -//! - [`AccountInfo`] — A description of a single Solana account. All accounts -//! that might be accessed by a program invocation are provided to the program -//! entrypoint as `AccountInfo`. -//! - [`Instruction`] — A directive telling the runtime to execute a program, -//! passing it a set of accounts and program-specific data. -//! - [`ProgramError`] and [`ProgramResult`] — The error type that all programs -//! must return, reported to the runtime as a `u64`. -//! - [`Sol`] — The Solana native token type, with conversions to and from -//! [_lamports_], the smallest fractional unit of SOL, in the [`native_token`] -//! module. -//! -//! [acc]: https://solana.com/docs/core/accounts -//! [`Pubkey`]: pubkey::Pubkey -//! [`Hash`]: hash::Hash -//! [`Instruction`]: instruction::Instruction -//! [`AccountInfo`]: account_info::AccountInfo -//! [`ProgramError`]: program_error::ProgramError -//! [`ProgramResult`]: entrypoint::ProgramResult -//! [ed25519]: https://ed25519.cr.yp.to/ -//! [`Keypair`]: https://docs.rs/solana-sdk/latest/solana_sdk/signer/keypair/struct.Keypair.html -//! [SHA-256]: https://en.wikipedia.org/wiki/SHA-2 -//! [`Sol`]: native_token::Sol -//! [_lamports_]: https://solana.com/docs/intro#what-are-sols -//! -//! # Serialization -//! -//! Within the Solana runtime, programs, and network, at least three different -//! serialization formats are used, and `solana-program` provides access to -//! those needed by programs. -//! -//! In user-written Solana program code, serialization is primarily used for -//! accessing [`AccountInfo`] data and [`Instruction`] data, both of which are -//! program-specific binary data. Every program is free to decide their own -//! serialization format, but data received from other sources — -//! [sysvars][sysvar] for example — must be deserialized using the methods -//! indicated by the documentation for that data or data type. -//! -//! [`AccountInfo`]: account_info::AccountInfo -//! [`Instruction`]: instruction::Instruction -//! -//! The three serialization formats in use in Solana are: -//! -//! - __[Borsh]__, a compact and well-specified format developed by the [NEAR] -//! project, suitable for use in protocol definitions and for archival storage. -//! It has a [Rust implementation][brust] and a [JavaScript implementation][bjs] -//! and is recommended for all purposes. -//! -//! Users need to import the [`borsh`] crate themselves — it is not -//! re-exported by `solana-program`, though this crate provides several useful -//! utilities in its [`borsh1` module][borshmod] that are not available in the -//! `borsh` library. -//! -//! The [`Instruction::new_with_borsh`] function creates an `Instruction` by -//! serializing a value with borsh. -//! -//! [Borsh]: https://borsh.io/ -//! [NEAR]: https://near.org/ -//! [brust]: https://docs.rs/borsh -//! [bjs]: https://github.com/near/borsh-js -//! [`borsh`]: https://docs.rs/borsh -//! [borshmod]: crate::borsh1 -//! [`Instruction::new_with_borsh`]: instruction::Instruction::new_with_borsh -//! -//! - __[Bincode]__, a compact serialization format that implements the [Serde] -//! Rust APIs. As it does not have a specification nor a JavaScript -//! implementation, and uses more CPU than borsh, it is not recommend for new -//! code. -//! -//! Many system program and native program instructions are serialized with -//! bincode, and it is used for other purposes in the runtime. In these cases -//! Rust programmers are generally not directly exposed to the encoding format -//! as it is hidden behind APIs. -//! -//! The [`Instruction::new_with_bincode`] function creates an `Instruction` by -//! serializing a value with bincode. -//! -//! [Bincode]: https://docs.rs/bincode -//! [Serde]: https://serde.rs/ -//! [`Instruction::new_with_bincode`]: instruction::Instruction::new_with_bincode -//! -//! - __[`Pack`]__, a Solana-specific serialization API that is used by many -//! older programs in the [Solana Program Library][spl] to define their -//! account format. It is difficult to implement and does not define a -//! language-independent serialization format. It is not generally recommended -//! for new code. -//! -//! [`Pack`]: https://docs.rs/solana-program-pack/latest/trait.Pack.html -//! -//! Developers should carefully consider the CPU cost of serialization, balanced -//! against the need for correctness and ease of use: off-the-shelf -//! serialization formats tend to be more expensive than carefully hand-written -//! application-specific formats; but application-specific formats are more -//! difficult to ensure the correctness of, and to provide multi-language -//! implementations for. It is not uncommon for programs to pack and unpack -//! their data with hand-written code. -//! -//! # Cross-program instruction execution -//! -//! Solana programs may call other programs, termed [_cross-program -//! invocation_][cpi] (CPI), with the [`invoke`] and [`invoke_signed`] -//! functions. When calling another program the caller must provide the -//! [`Instruction`] to be invoked, as well as the [`AccountInfo`] for every -//! account required by the instruction. Because the only way for a program to -//! acquire `AccountInfo` values is by receiving them from the runtime at the -//! [program entrypoint][entrypoint!], any account required by the callee -//! program must transitively be required by the caller program, and provided by -//! _its_ caller. -//! -//! [`invoke`]: program::invoke -//! [`invoke_signed`]: program::invoke_signed -//! [cpi]: https://solana.com/docs/core/cpi -//! -//! A simple example of transferring lamports via CPI: -//! -//! ``` -//! use solana_account_info::{next_account_info, AccountInfo}; -//! use solana_program_entrypoint::entrypoint; -//! use solana_program_error::ProgramResult; -//! use solana_cpi::invoke; -//! use solana_pubkey::Pubkey; -//! use solana_system_interface::instruction::transfer; -//! -//! entrypoint!(process_instruction); -//! -//! fn process_instruction( -//! program_id: &Pubkey, -//! accounts: &[AccountInfo], -//! instruction_data: &[u8], -//! ) -> ProgramResult { -//! let account_info_iter = &mut accounts.iter(); -//! -//! let payer = next_account_info(account_info_iter)?; -//! let recipient = next_account_info(account_info_iter)?; -//! -//! assert!(payer.is_writable); -//! assert!(payer.is_signer); -//! assert!(recipient.is_writable); -//! -//! let lamports = 1000000; -//! -//! invoke( -//! &transfer(payer.key, recipient.key, lamports), -//! &[payer.clone(), recipient.clone()], -//! ) -//! } -//! ``` -//! -//! Solana also includes a mechanism to let programs control and sign for -//! accounts without needing to protect a corresponding secret key, called -//! [_program derived addresses_][pdas]. PDAs are derived with the -//! [`Pubkey::find_program_address`] function. With a PDA, a program can call -//! `invoke_signed` to call another program while virtually "signing" for the -//! PDA. -//! -//! [pdas]: https://solana.com/docs/core/cpi#program-derived-addresses -//! [`Pubkey::find_program_address`]: pubkey::Pubkey::find_program_address -//! -//! A simple example of creating an account for a PDA: -//! -//! ``` -//! use solana_account_info::{next_account_info, AccountInfo}; -//! use solana_program_entrypoint::entrypoint; -//! use solana_program_error::ProgramResult; -//! use solana_cpi::invoke_signed; -//! use solana_pubkey::Pubkey; -//! use solana_system_interface::instruction::create_account; -//! -//! entrypoint!(process_instruction); -//! -//! fn process_instruction( -//! program_id: &Pubkey, -//! accounts: &[AccountInfo], -//! instruction_data: &[u8], -//! ) -> ProgramResult { -//! let account_info_iter = &mut accounts.iter(); -//! let payer = next_account_info(account_info_iter)?; -//! let vault_pda = next_account_info(account_info_iter)?; -//! let system_program = next_account_info(account_info_iter)?; -//! -//! assert!(payer.is_writable); -//! assert!(payer.is_signer); -//! assert!(vault_pda.is_writable); -//! assert_eq!(vault_pda.owner, &solana_system_interface::program::ID); -//! assert!(solana_system_interface::program::check_id(system_program.key)); -//! -//! let vault_bump_seed = instruction_data[0]; -//! let vault_seeds = &[b"vault", payer.key.as_ref(), &[vault_bump_seed]]; -//! let expected_vault_pda = Pubkey::create_program_address(vault_seeds, program_id)?; -//! -//! assert_eq!(vault_pda.key, &expected_vault_pda); -//! -//! let lamports = 10000000; -//! let vault_size = 16; -//! -//! invoke_signed( -//! &create_account( -//! &payer.key, -//! &vault_pda.key, -//! lamports, -//! vault_size, -//! &program_id, -//! ), -//! &[ -//! payer.clone(), -//! vault_pda.clone(), -//! ], -//! &[ -//! &[ -//! b"vault", -//! payer.key.as_ref(), -//! &[vault_bump_seed], -//! ], -//! ] -//! )?; -//! Ok(()) -//! } -//! ``` -//! -//! # Native programs -//! -//! Some solana programs are [_native programs_][np2], running native machine -//! code that is distributed with the runtime, with well-known program IDs. -//! -//! [np2]: https://docs.solanalabs.com/runtime/programs -//! -//! Some native programs can be [invoked][cpi] by other programs, but some can -//! only be executed as "top-level" instructions included by off-chain clients -//! in a [`Transaction`]. -//! -//! [`Transaction`]: https://docs.rs/solana-sdk/latest/solana_sdk/transaction/struct.Transaction.html -//! -//! This crate defines the program IDs for most native programs. Even though -//! some native programs cannot be invoked by other programs, a Solana program -//! may need access to their program IDs. For example, a program may need to -//! verify that an ed25519 signature verification instruction was included in -//! the same transaction as its own instruction. For many native programs, this -//! crate also defines enums that represent the instructions they process, and -//! constructors for building the instructions. -//! -//! Locations of program IDs and instruction constructors are noted in the list -//! below, as well as whether they are invokable by other programs. -//! -//! While some native programs have been active since the genesis block, others -//! are activated dynamically after a specific [slot], and some are not yet -//! active. This documentation does not distinguish which native programs are -//! active on any particular network. The `solana feature status` CLI command -//! can help in determining active features. -//! -//! [slot]: https://solana.com/docs/terminology#slot -//! -//! Native programs important to Solana program authors include: -//! -//! - __System Program__: Creates new accounts, allocates account data, assigns -//! accounts to owning programs, transfers lamports from System Program owned -//! accounts and pays transaction fees. -//! - ID: [`solana_system_interface::program::ID`](https://docs.rs/solana-system-interface/latest/solana_system_interface/program/constant.ID.html) -//! - Instruction: [`solana_system_interface::instruction`](https://docs.rs/solana-system-interface/latest/solana_system_interface/instruction/index.html) -//! - Invokable by programs? yes -//! -//! - __Compute Budget Program__: Requests additional CPU or memory resources -//! for a transaction. This program does nothing when called from another -//! program. -//! - ID: [`solana_compute_budget_interface::ID`](https://docs.rs/solana-compute-budget-interface/latest/solana_compute_budget_interface/constant.ID.html) -//! - Instruction: [`solana_compute_budget_interface::ComputeBudgetInstruction`](https://docs.rs/solana-compute-budget-interface/latest/solana_compute_budget_interface/enum.ComputeBudgetInstruction.html) -//! - Invokable by programs? no -//! -//! - __ed25519 Program__: Verifies an ed25519 signature. -//! - ID: [`solana_sdk_ids::ed25519_program::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/ed25519_program/constant.ID.html) -//! - Instruction: [`solana_ed25519_program::new_ed25519_instruction_with_signature`](https://docs.rs/solana-ed25519-program/latest/solana_ed25519_program/fn.new_ed25519_instruction_with_signature.html) -//! - Invokable by programs? no -//! -//! - __secp256k1 Program__: Verifies secp256k1 public key recovery operations. -//! - ID: [`solana_sdk_ids::secp256k1_program::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/secp256k1_program/constant.ID.html) -//! - Instruction: [`solana_secp256k1_program::new_secp256k1_instruction_with_signature`](https://docs.rs/solana-secp256k1-program/latest/solana_secp256k1_program/fn.new_secp256k1_instruction_with_signature.html) -//! - Invokable by programs? no -//! -//! - __BPF Loader__: Deploys, and executes immutable programs on the chain. -//! - ID: [`solana_sdk_ids::bpf_loader::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/bpf_loader/constant.ID.html) -//! - Instruction: [`solana_loader_v2_interface::instruction`](https://docs.rs/solana-loader-v2-interface/latest/solana_loader_v2_interface/instruction/index.html) -//! - Invokable by programs? yes -//! -//! - __Upgradable BPF Loader__: Deploys, upgrades, and executes upgradable -//! programs on the chain. -//! - ID: [`solana_sdk_ids::bpf_loader_upgradeable::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/bpf_loader_upgradeable/constant.ID.html) -//! - Instruction: [`solana_loader_v3_interface::instruction`](https://docs.rs/solana-loader-v3-interface/latest/solana_loader_v3_interface/instruction/index.html) -//! - Invokable by programs? yes -//! -//! - __Deprecated BPF Loader__: Deploys, and executes immutable programs on the -//! chain. -//! - ID: [`solana_sdk_ids::bpf_loader_deprecated::ID`](https://docs.rs/solana-sdk-ids/latest/solana_sdk_ids/bpf_loader_deprecated/constant.ID.html) -//! - Instruction: [`solana_loader_v2_interface::instruction`](https://docs.rs/solana-loader-v2-interface/latest/solana_loader_v2_interface/instruction/index.html) -//! - Invokable by programs? yes -//! -//! [lut]: https://docs.solanalabs.com/proposals/versioned-transactions - -#![allow(incomplete_features)] -#![cfg_attr(feature = "frozen-abi", feature(specialization))] -#![cfg_attr(docsrs, feature(doc_cfg))] - -// Allows macro expansion of `use ::solana_program::*` to work within this crate -extern crate self as solana_program; - -pub mod bpf_loader; -pub mod bpf_loader_deprecated; -pub mod compute_units; -pub mod ed25519_program; -pub mod entrypoint_deprecated; -pub mod epoch_schedule; -pub use solana_epoch_stake as epoch_stake; -pub mod hash; -pub mod incinerator; -pub mod instruction; -pub mod lamports; -pub mod log; -pub mod program; -pub mod program_error; -pub mod secp256k1_program; -pub mod slot_hashes; -pub mod slot_history; -pub mod syscalls; -pub mod sysvar; - -#[deprecated(since = "2.2.0", note = "Use `solana-big-mod-exp` crate instead")] -pub use solana_big_mod_exp as big_mod_exp; -#[deprecated(since = "2.2.0", note = "Use `solana-blake3-hasher` crate instead")] -pub use solana_blake3_hasher as blake3; -#[cfg(feature = "borsh")] -#[deprecated(since = "2.1.0", note = "Use `solana-borsh` crate instead")] -pub use solana_borsh::v1 as borsh1; -#[deprecated(since = "2.1.0", note = "Use `solana-epoch-rewards` crate instead")] -pub use solana_epoch_rewards as epoch_rewards; -#[deprecated(since = "2.1.0", note = "Use `solana-fee-calculator` crate instead")] -pub use solana_fee_calculator as fee_calculator; -#[deprecated(since = "2.2.0", note = "Use `solana-keccak-hasher` crate instead")] -pub use solana_keccak_hasher as keccak; -#[deprecated(since = "2.1.0", note = "Use `solana-last-restart-slot` crate instead")] -pub use solana_last_restart_slot as last_restart_slot; -#[deprecated(since = "2.1.0", note = "Use `solana-program-memory` crate instead")] -pub use solana_program_memory as program_memory; -#[deprecated(since = "2.1.0", note = "Use `solana-program-pack` crate instead")] -pub use solana_program_pack as program_pack; -#[deprecated(since = "2.1.0", note = "Use `solana-secp256k1-recover` crate instead")] -pub use solana_secp256k1_recover as secp256k1_recover; -#[deprecated(since = "2.1.0", note = "Use `solana-serde-varint` crate instead")] -pub use solana_serde_varint as serde_varint; -#[deprecated(since = "2.1.0", note = "Use `solana-serialize-utils` crate instead")] -pub use solana_serialize_utils as serialize_utils; -#[deprecated(since = "2.1.0", note = "Use `solana-short-vec` crate instead")] -pub use solana_short_vec as short_vec; -#[deprecated(since = "2.1.0", note = "Use `solana-stable-layout` crate instead")] -pub use solana_stable_layout as stable_layout; -#[cfg(not(target_os = "solana"))] -pub use solana_sysvar::program_stubs; -pub use { - solana_account_info::{self as account_info, debug_account_data}, - solana_clock as clock, - solana_msg::msg, - solana_native_token as native_token, - solana_program_entrypoint::{ - self as entrypoint, custom_heap_default, custom_panic_default, entrypoint, - entrypoint_no_alloc, - }, - solana_program_option as program_option, solana_pubkey as pubkey, solana_rent as rent, - solana_sysvar::impl_sysvar_get, -}; -/// The [config native program][np]. -/// -/// [np]: https://docs.solanalabs.com/runtime/programs#config-program -pub mod config { - pub mod program { - pub use solana_sdk_ids::config::{check_id, id, ID}; - } -} - -pub use solana_pubkey::{declare_deprecated_id, declare_id, pubkey}; -#[deprecated(since = "2.1.0", note = "Use `solana-sysvar-id` crate instead")] -pub use solana_sysvar_id::{declare_deprecated_sysvar_id, declare_sysvar_id}; - -/// Convenience macro for doing integer division where the operation's safety -/// can be checked at compile-time. -/// -/// Since `unchecked_div_by_const!()` is supposed to fail at compile-time, abuse -/// doctests to cover failure modes -/// -/// # Examples -/// -/// Literal denominator div-by-zero fails: -/// -/// ```compile_fail -/// # use solana_program::unchecked_div_by_const; -/// # fn main() { -/// let _ = unchecked_div_by_const!(10, 0); -/// # } -/// ``` -/// -/// Const denominator div-by-zero fails: -/// -/// ```compile_fail -/// # use solana_program::unchecked_div_by_const; -/// # fn main() { -/// const D: u64 = 0; -/// let _ = unchecked_div_by_const!(10, D); -/// # } -/// ``` -/// -/// Non-const denominator fails: -/// -/// ```compile_fail -/// # use solana_program::unchecked_div_by_const; -/// # fn main() { -/// let d = 0; -/// let _ = unchecked_div_by_const!(10, d); -/// # } -/// ``` -/// -/// Literal denominator div-by-zero fails: -/// -/// ```compile_fail -/// # use solana_program::unchecked_div_by_const; -/// # fn main() { -/// const N: u64 = 10; -/// let _ = unchecked_div_by_const!(N, 0); -/// # } -/// ``` -/// -/// Const denominator div-by-zero fails: -/// -/// ```compile_fail -/// # use solana_program::unchecked_div_by_const; -/// # fn main() { -/// const N: u64 = 10; -/// const D: u64 = 0; -/// let _ = unchecked_div_by_const!(N, D); -/// # } -/// ``` -/// -/// Non-const denominator fails: -/// -/// ```compile_fail -/// # use solana_program::unchecked_div_by_const; -/// # fn main() { -/// # const N: u64 = 10; -/// let d = 0; -/// let _ = unchecked_div_by_const!(N, d); -/// # } -/// ``` -/// -/// Literal denominator div-by-zero fails: -/// -/// ```compile_fail -/// # use solana_program::unchecked_div_by_const; -/// # fn main() { -/// let n = 10; -/// let _ = unchecked_div_by_const!(n, 0); -/// # } -/// ``` -/// -/// Const denominator div-by-zero fails: -/// -/// ```compile_fail -/// # use solana_program::unchecked_div_by_const; -/// # fn main() { -/// let n = 10; -/// const D: u64 = 0; -/// let _ = unchecked_div_by_const!(n, D); -/// # } -/// ``` -/// -/// Non-const denominator fails: -/// -/// ```compile_fail -/// # use solana_program::unchecked_div_by_const; -/// # fn main() { -/// let n = 10; -/// let d = 0; -/// let _ = unchecked_div_by_const!(n, d); -/// # } -/// ``` -#[macro_export] -macro_rules! unchecked_div_by_const { - ($num:expr, $den:expr) => {{ - // Ensure the denominator is compile-time constant - let _ = [(); ($den - $den) as usize]; - // Compile-time constant integer div-by-zero passes for some reason - // when invoked from a compilation unit other than that where this - // macro is defined. Do an explicit zero-check for now. Sorry about the - // ugly error messages! - // https://users.rust-lang.org/t/unexpected-behavior-of-compile-time-integer-div-by-zero-check-in-declarative-macro/56718 - let _ = [(); ($den as usize) - 1]; - #[allow(clippy::arithmetic_side_effects)] - let quotient = $num / $den; - quotient - }}; -} - -// This re-export is purposefully listed after all other exports: because of an -// interaction within rustdoc between the reexports inside this module of -// `solana_program`'s top-level modules, and `solana_sdk`'s glob re-export of -// `solana_program`'s top-level modules, if this re-export is not lexically last -// rustdoc fails to generate documentation for the re-exports within -// `solana_sdk`. -#[deprecated(since = "2.2.0", note = "Use solana-example-mocks instead")] -#[cfg(not(target_os = "solana"))] -pub use solana_example_mocks as example_mocks; - -#[cfg(test)] -mod tests { - #[test] - fn test_unchecked_div_by_const() { - const D: u64 = 2; - const N: u64 = 10; - let n = 10; - assert_eq!(unchecked_div_by_const!(10, 2), 5); - assert_eq!(unchecked_div_by_const!(N, 2), 5); - assert_eq!(unchecked_div_by_const!(n, 2), 5); - assert_eq!(unchecked_div_by_const!(10, D), 5); - assert_eq!(unchecked_div_by_const!(N, D), 5); - assert_eq!(unchecked_div_by_const!(n, D), 5); - } -} diff --git a/sp4/solana-program-4.0.0/src/log.rs b/sp4/solana-program-4.0.0/src/log.rs deleted file mode 100644 index 049e286c..00000000 --- a/sp4/solana-program-4.0.0/src/log.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Logging utilities for Rust-based Solana programs. -//! -//! Logging is the main mechanism for getting debugging information out of -//! running Solana programs, and there are several functions available for doing -//! so efficiently, depending on the type of data being logged. -//! -//! The most common way to emit logs is through the [`msg!`] macro, which logs -//! simple strings, as well as [formatted strings][fs]. -//! -//! [`msg!`]: crate::msg! -//! [fs]: https://doc.rust-lang.org/std/fmt/ -//! -//! Logs can be viewed in multiple ways: -//! -//! - The `solana logs` command displays logs for all transactions executed on a -//! network. Note though that transactions that fail during pre-flight -//! simulation are not displayed here. -//! - When submitting transactions via [`RpcClient`], if Rust's own logging is -//! active then the `solana_rpc_client` crate logs at the "debug" level any logs -//! for transactions that failed during simulation. If using [`env_logger`] -//! these logs can be activated by setting `RUST_LOG=solana_rpc_client=debug`. -//! - Logs can be retrieved from a finalized transaction by calling -//! [`RpcClient::get_transaction`]. -//! - Block explorers may display logs. -//! -//! [`RpcClient`]: https://docs.rs/solana-rpc-client/latest/solana_rpc_client/rpc_client/struct.RpcClient.html -//! [`env_logger`]: https://docs.rs/env_logger -//! [`RpcClient::get_transaction`]: https://docs.rs/solana-rpc-client/latest/solana_rpc_client/rpc_client/struct.RpcClient.html#method.get_transaction -//! -//! While most logging functions are defined in this module, [`Pubkey`]s can -//! also be efficiently logged with the [`Pubkey::log`] function. -//! -//! [`Pubkey`]: crate::pubkey::Pubkey -//! [`Pubkey::log`]: crate::pubkey::Pubkey::log - -use crate::account_info::AccountInfo; -pub use solana_msg::{msg, sol_log}; - -/// Print 64-bit values represented as hexadecimal to the log. -#[inline] -pub fn sol_log_64(arg1: u64, arg2: u64, arg3: u64, arg4: u64, arg5: u64) { - #[cfg(target_os = "solana")] - unsafe { - crate::syscalls::sol_log_64_(arg1, arg2, arg3, arg4, arg5); - } - - #[cfg(not(target_os = "solana"))] - crate::program_stubs::sol_log_64(arg1, arg2, arg3, arg4, arg5); -} - -/// Print some slices as base64. -pub fn sol_log_data(data: &[&[u8]]) { - #[cfg(target_os = "solana")] - unsafe { - crate::syscalls::sol_log_data(data as *const _ as *const u8, data.len() as u64) - }; - - #[cfg(not(target_os = "solana"))] - crate::program_stubs::sol_log_data(data); -} - -/// Print the hexadecimal representation of a slice. -pub fn sol_log_slice(slice: &[u8]) { - for (i, s) in slice.iter().enumerate() { - sol_log_64(0, 0, 0, i as u64, *s as u64); - } -} - -/// Print the hexadecimal representation of the program's input parameters. -/// -/// - `accounts` - A slice of [`AccountInfo`]. -/// - `data` - The instruction data. -pub fn sol_log_params(accounts: &[AccountInfo], data: &[u8]) { - for (i, account) in accounts.iter().enumerate() { - msg!("AccountInfo"); - sol_log_64(0, 0, 0, 0, i as u64); - msg!("- Is signer"); - sol_log_64(0, 0, 0, 0, account.is_signer as u64); - msg!("- Key"); - account.key.log(); - msg!("- Lamports"); - sol_log_64(0, 0, 0, 0, account.lamports()); - msg!("- Account data length"); - sol_log_64(0, 0, 0, 0, account.data_len() as u64); - msg!("- Owner"); - account.owner.log(); - } - msg!("Instruction data"); - sol_log_slice(data); -} - -/// Print the remaining compute units available to the program. -#[inline] -pub fn sol_log_compute_units() { - #[cfg(target_os = "solana")] - unsafe { - crate::syscalls::sol_log_compute_units_(); - } - #[cfg(not(target_os = "solana"))] - crate::program_stubs::sol_log_compute_units(); -} diff --git a/sp4/solana-program-4.0.0/src/program.rs b/sp4/solana-program-4.0.0/src/program.rs deleted file mode 100644 index 6816a805..00000000 --- a/sp4/solana-program-4.0.0/src/program.rs +++ /dev/null @@ -1,215 +0,0 @@ -//! Wrappers around [`solana-cpi`] with support for overwriting -//! syscall stubs -//! -//! Solana programs may call other programs, termed [_cross-program -//! invocations_][cpi] (CPI), with the [`invoke`] and [`invoke_signed`] -//! functions. -//! -//! [`solana-cpi`]: https://docs.rs/solana-cpi/latest/solana_cpi/ -//! [`invoke`]: invoke -//! [`invoke_signed`]: invoke_signed -//! [cpi]: https://solana.com/docs/core/cpi - -use crate::{ - account_info::AccountInfo, entrypoint::ProgramResult, instruction::Instruction, pubkey::Pubkey, - stable_layout::stable_instruction::StableInstruction, -}; -pub use solana_cpi::MAX_RETURN_DATA; - -/// Like [`solana_cpi::invoke`], but with support -/// for overwriting the `sol_invoke_signed` syscall stub. -/// -/// [`solana_cpi::invoke`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke.html -pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult { - invoke_signed(instruction, account_infos, &[]) -} - -/// Like [`solana_cpi::invoke_unchecked`], but with support -/// for overwriting the `sol_invoke_signed` syscall stub. -/// -/// [`solana_cpi::invoke_unchecked`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_unchecked.html -/// -/// # Safety -/// -/// __This function is incorrectly missing an `unsafe` declaration.__ -/// -/// If any of the writable accounts passed to the callee contain data that is -/// borrowed within the calling program, and that data is written to by the -/// callee, then Rust's aliasing rules will be violated and cause undefined -/// behavior. -pub fn invoke_unchecked(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult { - invoke_signed_unchecked(instruction, account_infos, &[]) -} - -/// Like [`solana_cpi::invoke_signed`], but with support -/// for overwriting the `sol_invoke_signed` syscall stub. -/// -/// [`solana_cpi::invoke_signed`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed.html -pub fn invoke_signed( - instruction: &Instruction, - account_infos: &[AccountInfo], - signers_seeds: &[&[&[u8]]], -) -> ProgramResult { - // Check that the account RefCells are consistent with the request - for account_meta in instruction.accounts.iter() { - for account_info in account_infos.iter() { - if account_meta.pubkey == *account_info.key { - if account_meta.is_writable { - let _ = account_info.try_borrow_mut_lamports()?; - let _ = account_info.try_borrow_mut_data()?; - } else { - let _ = account_info.try_borrow_lamports()?; - let _ = account_info.try_borrow_data()?; - } - break; - } - } - } - - invoke_signed_unchecked(instruction, account_infos, signers_seeds) -} - -/// Like [`solana_cpi::invoke_signed_unchecked`], but with support -/// for overwriting the `sol_invoke_signed` syscall stub. -/// -/// [`solana_cpi::invoke_signed_unchecked`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.invoke_signed_unchecked.html -/// -/// # Safety -/// -/// __This function is incorrectly missing an `unsafe` declaration.__ -/// -/// If any of the writable accounts passed to the callee contain data that is -/// borrowed within the calling program, and that data is written to by the -/// callee, then Rust's aliasing rules will be violated and cause undefined -/// behavior. -pub fn invoke_signed_unchecked( - instruction: &Instruction, - account_infos: &[AccountInfo], - signers_seeds: &[&[&[u8]]], -) -> ProgramResult { - #[cfg(target_os = "solana")] - { - solana_cpi::invoke_signed_unchecked(instruction, account_infos, signers_seeds) - } - - #[cfg(not(target_os = "solana"))] - crate::program_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds) -} - -/// Like [`solana_cpi::set_return_data`], but with support -/// for overwriting the `sol_set_return_data` syscall stub. -/// -/// [`solana_cpi::set_return_data`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.set_return_data.html -pub fn set_return_data(data: &[u8]) { - #[cfg(target_os = "solana")] - { - solana_cpi::set_return_data(data); - } - - #[cfg(not(target_os = "solana"))] - crate::program_stubs::sol_set_return_data(data) -} - -/// Like [`solana_cpi::get_return_data`], but with support -/// for overwriting the `sol_get_return_data` syscall stub. -/// -/// [`solana_cpi::get_return_data`]: https://docs.rs/solana-cpi/latest/solana_cpi/fn.get_return_data.html -pub fn get_return_data() -> Option<(Pubkey, Vec)> { - #[cfg(target_os = "solana")] - { - solana_cpi::get_return_data() - } - - #[cfg(not(target_os = "solana"))] - crate::program_stubs::sol_get_return_data() -} - -/// Do sanity checks of type layout. -#[doc(hidden)] -#[allow(clippy::arithmetic_side_effects)] -pub fn check_type_assumptions() { - extern crate memoffset; - use { - crate::instruction::AccountMeta, - memoffset::offset_of, - std::{ - mem::{align_of, size_of}, - str::FromStr, - }, - }; - - // Code in this file assumes that u64 and usize are the same - assert_eq!(size_of::(), size_of::()); - // Code in this file assumes that u8 is byte aligned - assert_eq!(1, align_of::()); - - // Enforce Instruction layout - { - assert_eq!(size_of::(), 32 + 1 + 1); - - let pubkey1 = Pubkey::from_str("J9PYCcoKusHyKRMXnBL17VTXC3MVETyqBG2KyLXVv6Ai").unwrap(); - let pubkey2 = Pubkey::from_str("Hvy4GHgPToZNoENTKjC4mJqpzWWjgTwXrFufKfxYiKkV").unwrap(); - let pubkey3 = Pubkey::from_str("JDMyRL8rCkae7maCSv47upNuBMFd3Mgos1fz2AvYzVzY").unwrap(); - let account_meta1 = AccountMeta { - pubkey: pubkey2, - is_signer: true, - is_writable: false, - }; - let account_meta2 = AccountMeta { - pubkey: pubkey3, - is_signer: false, - is_writable: true, - }; - let data = vec![1, 2, 3, 4, 5]; - let instruction = Instruction { - program_id: pubkey1, - accounts: vec![account_meta1.clone(), account_meta2.clone()], - data: data.clone(), - }; - let instruction = StableInstruction::from(instruction); - let instruction_addr = &instruction as *const _ as u64; - - // program id - assert_eq!(offset_of!(StableInstruction, program_id), 48); - let pubkey_ptr = (instruction_addr + 48) as *const Pubkey; - unsafe { - assert_eq!(*pubkey_ptr, pubkey1); - } - - // accounts - assert_eq!(offset_of!(StableInstruction, accounts), 0); - let accounts_ptr = (instruction_addr) as *const *const AccountMeta; - let accounts_cap = (instruction_addr + 8) as *const usize; - let accounts_len = (instruction_addr + 16) as *const usize; - unsafe { - assert_eq!(*accounts_cap, 2); - assert_eq!(*accounts_len, 2); - let account_meta_ptr = *accounts_ptr; - assert_eq!(*account_meta_ptr, account_meta1); - assert_eq!(*(account_meta_ptr.offset(1)), account_meta2); - } - - // data - assert_eq!(offset_of!(StableInstruction, data), 24); - let data_ptr = (instruction_addr + 24) as *const *const [u8; 5]; - let data_cap = (instruction_addr + 24 + 8) as *const usize; - let data_len = (instruction_addr + 24 + 16) as *const usize; - unsafe { - assert_eq!(*data_cap, 5); - - assert_eq!(*data_len, 5); - let u8_ptr = *data_ptr; - assert_eq!(*u8_ptr, data[..]); - } - } - - solana_account_info::check_type_assumptions(); -} - -#[cfg(test)] -mod tests { - #[test] - fn test_check_type_assumptions() { - super::check_type_assumptions() - } -} diff --git a/sp4/solana-program-4.0.0/src/program_error.rs b/sp4/solana-program-4.0.0/src/program_error.rs deleted file mode 100644 index 975a5e31..00000000 --- a/sp4/solana-program-4.0.0/src/program_error.rs +++ /dev/null @@ -1,14 +0,0 @@ -pub use { - solana_instruction_error::{ - ACCOUNT_ALREADY_INITIALIZED, ACCOUNT_BORROW_FAILED, ACCOUNT_DATA_TOO_SMALL, - ACCOUNT_NOT_RENT_EXEMPT, ARITHMETIC_OVERFLOW, BORSH_IO_ERROR, - BUILTIN_PROGRAMS_MUST_CONSUME_COMPUTE_UNITS, CUSTOM_ZERO, ILLEGAL_OWNER, IMMUTABLE, - INCORRECT_AUTHORITY, INCORRECT_PROGRAM_ID, INSUFFICIENT_FUNDS, INVALID_ACCOUNT_DATA, - INVALID_ACCOUNT_DATA_REALLOC, INVALID_ACCOUNT_OWNER, INVALID_ARGUMENT, - INVALID_INSTRUCTION_DATA, INVALID_SEEDS, MAX_ACCOUNTS_DATA_ALLOCATIONS_EXCEEDED, - MAX_INSTRUCTION_TRACE_LENGTH_EXCEEDED, MAX_SEED_LENGTH_EXCEEDED, - MISSING_REQUIRED_SIGNATURES, NOT_ENOUGH_ACCOUNT_KEYS, UNINITIALIZED_ACCOUNT, - UNSUPPORTED_SYSVAR, - }, - solana_program_error::ProgramError, -}; diff --git a/sp4/solana-program-4.0.0/src/secp256k1_program.rs b/sp4/solana-program-4.0.0/src/secp256k1_program.rs deleted file mode 100644 index 54a9f836..00000000 --- a/sp4/solana-program-4.0.0/src/secp256k1_program.rs +++ /dev/null @@ -1,9 +0,0 @@ -//! The [secp256k1 native program][np]. -//! -//! [np]: https://docs.solanalabs.com/runtime/programs#secp256k1-program -//! -//! Constructors for secp256k1 program instructions, and documentation on the -//! program's usage can be found in [`solana_sdk::secp256k1_instruction`]. -//! -//! [`solana_sdk::secp256k1_instruction`]: https://docs.rs/solana-sdk/latest/solana_sdk/secp256k1_instruction/index.html -pub use solana_sdk_ids::secp256k1_program::{check_id, id, ID}; diff --git a/sp4/solana-program-4.0.0/src/slot_hashes.rs b/sp4/solana-program-4.0.0/src/slot_hashes.rs deleted file mode 100644 index 7113b234..00000000 --- a/sp4/solana-program-4.0.0/src/slot_hashes.rs +++ /dev/null @@ -1 +0,0 @@ -pub use {solana_clock::Slot, solana_slot_hashes::*}; diff --git a/sp4/solana-program-4.0.0/src/slot_history.rs b/sp4/solana-program-4.0.0/src/slot_history.rs deleted file mode 100644 index 982c0cac..00000000 --- a/sp4/solana-program-4.0.0/src/slot_history.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[deprecated(since = "2.1.0", note = "Use `solana-slot-history` crate instead")] -pub use {solana_clock::Slot, solana_slot_history::*}; diff --git a/sp4/solana-program-4.0.0/src/syscalls/definitions.rs b/sp4/solana-program-4.0.0/src/syscalls/definitions.rs deleted file mode 100644 index 1eba1111..00000000 --- a/sp4/solana-program-4.0.0/src/syscalls/definitions.rs +++ /dev/null @@ -1,39 +0,0 @@ -#[deprecated(since = "2.1.0", note = "Use `solana_cpi::syscalls` instead")] -pub use solana_cpi::syscalls::{ - sol_get_return_data, sol_invoke_signed_c, sol_invoke_signed_rust, sol_set_return_data, -}; -#[deprecated( - since = "2.2.0", - note = "Use `solana_define_syscall::definitions` instead" -)] -pub use solana_define_syscall::definitions::{ - sol_alt_bn128_compression, sol_alt_bn128_group_op, sol_big_mod_exp, sol_blake3, - sol_curve_group_op, sol_curve_multiscalar_mul, sol_curve_pairing_map, sol_curve_validate_point, - sol_get_clock_sysvar, sol_get_epoch_rewards_sysvar, sol_get_epoch_schedule_sysvar, - sol_get_epoch_stake, sol_get_fees_sysvar, sol_get_last_restart_slot, sol_get_rent_sysvar, - sol_get_sysvar, sol_keccak256, sol_remaining_compute_units, -}; -#[cfg(target_feature = "static-syscalls")] -pub use solana_define_syscall::sys_hash; -#[deprecated(since = "2.1.0", note = "Use `solana_instruction::syscalls` instead")] -pub use solana_instruction::syscalls::{ - sol_get_processed_sibling_instruction, sol_get_stack_height, -}; -#[deprecated(since = "2.1.0", note = "Use `solana_msg::syscalls` instead.")] -pub use solana_msg::syscalls::{sol_log_, sol_log_64_, sol_log_compute_units_, sol_log_data}; -#[deprecated( - since = "2.1.0", - note = "Use `solana_program_memory::syscalls` instead" -)] -pub use solana_program_memory::syscalls::{sol_memcmp_, sol_memcpy_, sol_memmove_, sol_memset_}; -#[deprecated(since = "2.1.0", note = "Use `solana_pubkey::syscalls` instead")] -pub use solana_pubkey::syscalls::{ - sol_create_program_address, sol_log_pubkey, sol_try_find_program_address, -}; -#[deprecated( - since = "2.1.0", - note = "Use `solana_secp256k1_recover::sol_secp256k1_recover` instead" -)] -pub use solana_secp256k1_recover::sol_secp256k1_recover; -#[deprecated(since = "2.1.0", note = "Use solana_sha256_hasher::sol_sha256 instead")] -pub use solana_sha256_hasher::sol_sha256; diff --git a/sp4/solana-program-4.0.0/src/syscalls/mod.rs b/sp4/solana-program-4.0.0/src/syscalls/mod.rs deleted file mode 100644 index 740b5457..00000000 --- a/sp4/solana-program-4.0.0/src/syscalls/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Declarations of Solana program syscalls. -//! -//! This module is mostly empty when not compiling for BPF targets. - -#[cfg(target_os = "solana")] -mod definitions; - -#[cfg(target_os = "solana")] -pub use definitions::*; - -/// Maximum CPI instruction data size. 10 KiB was chosen to ensure that CPI -/// instructions are not more limited than transaction instructions if the size -/// of transactions is doubled in the future. -pub const MAX_CPI_INSTRUCTION_DATA_LEN: u64 = 10 * 1024; - -/// Maximum CPI instruction accounts. 255 was chosen to ensure that instruction -/// accounts are always within the maximum instruction account limit for SBF -/// program instructions. -pub const MAX_CPI_INSTRUCTION_ACCOUNTS: u8 = u8::MAX; - -/// Maximum number of account info structs that can be used in a single CPI -/// invocation. A limit on account info structs is effectively the same as -/// limiting the number of unique accounts. 128 was chosen to match the max -/// number of locked accounts per transaction (MAX_TX_ACCOUNT_LOCKS). -pub const MAX_CPI_ACCOUNT_INFOS: usize = 128; diff --git a/sp4/solana-program-4.0.0/src/sysvar.rs b/sp4/solana-program-4.0.0/src/sysvar.rs deleted file mode 100644 index cd17415c..00000000 --- a/sp4/solana-program-4.0.0/src/sysvar.rs +++ /dev/null @@ -1,33 +0,0 @@ -#[deprecated(since = "2.1.0", note = "Use `solana-sysvar-id` crate instead")] -pub use solana_sysvar_id::{declare_deprecated_sysvar_id, declare_sysvar_id, SysvarId}; -#[deprecated(since = "2.2.0", note = "Use `solana-sysvar` crate instead")] -#[allow(deprecated)] -pub use { - solana_sdk_ids::sysvar::{check_id, id, ID}, - solana_sysvar::{ - clock, epoch_rewards, epoch_schedule, fees, last_restart_slot, recent_blockhashes, rent, - rewards, slot_hashes, slot_history, Sysvar, SysvarSerialize, - }, -}; - -pub mod instructions { - #[deprecated(since = "2.2.0", note = "Use solana-instruction crate instead")] - pub use solana_instruction::{BorrowedAccountMeta, BorrowedInstruction}; - #[cfg(not(target_os = "solana"))] - #[deprecated(since = "2.2.0", note = "Use solana-instructions-sysvar crate instead")] - pub use solana_instructions_sysvar::construct_instructions_data; - #[cfg(all(not(target_os = "solana"), feature = "dev-context-only-utils"))] - #[deprecated(since = "2.2.0", note = "Use solana-instructions-sysvar crate instead")] - pub use solana_instructions_sysvar::serialize_instructions; - #[cfg(feature = "dev-context-only-utils")] - #[deprecated(since = "2.2.0", note = "Use solana-instructions-sysvar crate instead")] - pub use solana_instructions_sysvar::{deserialize_instruction, load_instruction_at}; - #[deprecated(since = "2.2.0", note = "Use solana-instructions-sysvar crate instead")] - #[allow(deprecated)] - pub use solana_instructions_sysvar::{ - get_instruction_relative, load_current_index_checked, load_instruction_at_checked, - Instructions, - }; - #[deprecated(since = "2.2.0", note = "Use solana-sdk-ids crate instead")] - pub use solana_sdk_ids::sysvar::instructions::{check_id, id, ID}; -} diff --git a/sp4/sp.crate b/sp4/sp.crate deleted file mode 100644 index bd7046c384b1f299a6116f5dbc2b2b6ea8a75e1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 30262 zcmV(#K;*w4iwFn+00002|8s9_VQyh9aB^>Fa$#*PG%heMFfL2L1;ugAZ@_Zz>Le~#b(jnnf3r|0%uulF1IhX4Of?>8Ud-)gZ8=bg@P{+Isy$3ds_ z=wh;bbkdPO`LMVQ9X>sB+$ivzz>i~R*!QEsXy^s5GwSt+UXu0`Cv&rO*c&;Ixb4>? zdCX0?ykx=1x{(K%HTCG=*WY~n_pkbI(X{`c%l1FMn#FJS@{W!B-|+^X)2r`)XApec z{~zMtpR)lO~hpWO{LY@HU+rQCV&yNn`%QSwoSY68_k|cF|k(cz`Aj}*u>jzGhB)!b(r%Bdx zvUuS6SuY8*I8CB3^W7*4`@J9>^_@ZHy56IMB)yTIPUA_s;EAUPJT#q8-lpTS7e@zY zZ5^G5xAQcs^s{&D9gp_h&p{Bn>Cka~ukU0@=%m>&Y{3u+cP7p6^!|(3B{eC>im;gTw zgMQ}9x$pIcqby5?i8~k$q9lp~4u6-e;*Qw%QJf89CMoRkzkZx;Xn)|>Tkr0Dmk->? zbNhbg1a9A9nG79fz)k&L>cxIStDVt^1_%A#FpF62qh#b{{XswNWl_ZU8=GGne&;yY z`|uB0)=uaJ!)(w?f;by^OvYfC$>))mgs$&o(J&njd+d{b9FJ1E>A3yjs2>JNziszM zlW9Cl(pvMqV{gyRkDSc!rz77PB*TI04&p)B4-%IN7&vj@q5=eNj9hhCX8lnw?v27M zW7PzSlO%DNGz!^8I;ZKak`>XLU4<8mO6T0;(Xqeh&be;jBo6DGK8;d8@V&I}25gMj zWv{2c=f&|bj6&B5qe0-io~Sk1F!9*xnK$xV=Vo~w-qf4#?0sqV(6M~ZKfgB`4SZ>L z)bly-oGkQRKXRjZ7$pH4!j0ViC~%XQZO#SWD2xI>bQ|YpdCOO;>kYGW6~0Nm+QRD} z_xHW_5IXAE~G)vNgF#? zoY_YQ)&VFVV)LKuqC3$)$FnI~Pa$R#}$LYXZ^f?S0XDy}>XZ z1nwweM!k#`pcEQr)gO%l6o5GH#}UI3_d^#=0MR&12ZJz5IG4hK(>O3nC!EAxH6|N+ zI-4&pAAIWjzQ-&^>A>kl!7v#O{lxL;4;E`0`5wn=(nsgr><=$>24T-14jTJCo5txlPOq+1r!fiR?sXW?XI<5l+Lx~S{P)$&{Ia;6 zZW^1PaVf3oFOIwCVF}P~9e1Eke}CkqnI8>NgB`c$^PP)czzT33v}AvfIX>!e9Q9LF z-$9xn09zH9MSoape1GrSOOy?V5p6|Sp?JkiEgECr9S)OWA9cWs@V|`uE`lz_PVrpk z(d$M1D2QEDv&?T6(UYl>TWvG*wl0R8z4vU$WH2uYV!EHiEI$?~HVPW%0DUKm;{X{H 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 N{s%Xdak>B)0sxCJ#&rMy From 6e8daf00251e884e4a4bf01d8a92a7115569ebdf Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 17:50:36 -0300 Subject: [PATCH 28/83] fix(cli): verify-setup now verifies the deployed key, and pin every negative case MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit verify-setup regenerated a key, printed its hash and exited zero. It compared against a digest the caller supplied and never read the key compiled into the program, so running it with no arguments verified nothing while the threat model claimed it let a third party confirm the deployed key matches this circuit. It now compares element by element — alpha, beta, gamma, delta and every IC point — against the program's baked key, and the expected digest is published so there is something to check against. With a wrong seed it names which elements diverge. That is the whole point: a ceremony verifier comparing only delta, as a competing submission does, would certify a key belonging to a different circuit. Seven negative tests asserted only that something failed. The standard PROOF.md sets is that a negative case is evidence only if this program's own error code is what rejected it, and these would have passed on a runtime failure or an account miscount. Each now pins its code. The one case genuinely caught by the runtime before the program can rule on it asserts that explicitly, so the exception is visible rather than looking like an oversight. Adds a test for the vault-in-action-list refusal, which the threat model asserted and nothing checked, and replaces an assertion comparing two freshly generated keypairs — trivially unequal, proving nothing about the property it named. PLAN.md now records the six things that did not ship as decisions with reasons, including one claim withdrawn rather than descoped: a program cannot check funding provenance, so a metric gating settlement was never deliverable. --- README.md | 6 +- crates/mirror-cli/src/main.rs | 53 ++++++++++++-- docs/PLAN.md | 34 ++++++++- docs/THREAT_MODEL.md | 15 +++- programs/mirror-pool/tests/end_to_end.rs | 90 +++++++++++++++++++++++- 5 files changed, 186 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 82995348..9d7f3407 100644 --- a/README.md +++ b/README.md @@ -174,8 +174,10 @@ multi-party ceremony, not more SOL. action side and measured on the membership side. - Not that the trusted setup is secure. It is *reproducible*, which is a different and lesser property: the seed is public, so the toxic waste is - public, so proofs are forgeable. `mirror verify-setup` lets anyone re-derive - the deployed key and check it against the circuit here. A competing submission + public, so proofs are forgeable. `mirror verify-setup` re-derives the key from + the public seed and compares it element by element against the one compiled + into the program — expected digest + `b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7`. A competing submission publishes its entropy string *and* gitignores its proving key, so its setup is insecure and unreproducible at once — no third party can produce a valid proof for its deployed program at all. diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 529cca61..f9fffb2a 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -503,15 +503,60 @@ fn main() -> Result<()> { Command::VerifySetup { seed, expect } => { let keys = mirror_circuit::generate_reproducible(seed.as_bytes()) .map_err(|e| anyhow::anyhow!("{e}"))?; - let digest = vk_digest(&keys.solana_vk()); - println!("vk sha256: {digest}"); + let regenerated = keys.solana_vk(); + let digest = vk_digest(®enerated); + println!("vk sha256 (regenerated): {digest}"); + + // Compare against the key actually compiled into the program, not + // only against a digest the caller supplies. Printing a hash and + // exiting zero verifies nothing; the point of a reproducible setup + // is that a third party can confirm the *deployed* key is the one + // this circuit and this seed produce. + let baked = mirror_pool_program::vk::VERIFYING_KEY; + let mut mismatches: Vec<&str> = Vec::new(); + if regenerated.alpha_g1 != baked.vk_alpha_g1 { + mismatches.push("alpha_g1"); + } + if regenerated.beta_g2 != baked.vk_beta_g2 { + mismatches.push("beta_g2"); + } + if regenerated.gamma_g2 != baked.vk_gamme_g2 { + mismatches.push("gamma_g2"); + } + if regenerated.delta_g2 != baked.vk_delta_g2 { + mismatches.push("delta_g2"); + } + if regenerated.ic.len() != baked.vk_ic.len() + || regenerated.ic.iter().zip(baked.vk_ic).any(|(a, b)| a != b) + { + mismatches.push("ic"); + } + + if mismatches.is_empty() { + println!( + "MATCH — every element of the program's verifying key is reproduced \ + by this seed and this circuit ({} IC points checked)", + regenerated.ic.len() + ); + } else { + eprintln!( + "MISMATCH against the program's baked key: {}", + mismatches.join(", ") + ); + eprintln!( + "The deployed program does not verify proofs from this circuit. \ + A ceremony verifier that checked only delta would have passed this." + ); + std::process::exit(1); + } + match expect { Some(want) if want.eq_ignore_ascii_case(&digest) => { - println!("MATCH — this seed and this circuit produce that key"); + println!("and it matches the digest you supplied"); Ok(()) } Some(want) => { - eprintln!("MISMATCH — expected {want}"); + eprintln!("but the digest you supplied was {want}"); std::process::exit(1); } None => Ok(()), diff --git a/docs/PLAN.md b/docs/PLAN.md index 59dd99a7..d00576fe 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -26,6 +26,8 @@ narrower and defensible: 3. **The measurement is load-bearing.** A pool that cannot demonstrate an effective-`k` above a floor refuses to settle. The metric is a protocol parameter, not a marketing line. + *(Withdrawn during implementation — see "What actually got cut". A program + cannot check provenance, so this claim was not deliverable.)* If we cannot support a claim with a measurement whose method we publish, we do not make the claim. @@ -54,6 +56,9 @@ nullifier, and directs the pool to execute one action disbursing exactly ``` note = (k, r) secret nullifier preimage, blinding commitment = Poseidon(k, r) the Merkle leaf + (shipped as Poseidon(k, r, denom_tag) — + binding the denomination in is cheap + defence in depth) nullifier = Poseidon(k) revealed on spend, spent once ever ``` @@ -137,7 +142,8 @@ competitor's implementation and asserted by its own test. A separate crate collects real mainnet funding data and reports effective anonymity at several label resolutions, with the raw sample committed so results -reproduce without RPC access. Methodology is being finalised; the design targets +reproduce without RPC access. The methodology is `docs/PROVENANCE_METHOD.md`; it +targets the specific weaknesses found in the two published attempts: selection bias that inflates the untraceable bucket, a sample spanning four seconds of chain time presented as a population, and a class key defined at raw-address resolution so @@ -189,6 +195,32 @@ The provenance sample shrinks before its method weakens. **The accounting invariant, the negative tests, the honest limitations section and a green CI are never cut** — they are what separates this from the submissions it means to beat. +### What actually got cut, and why + +This plan is as written on day zero. Six things in it did not ship, and each was +a decision rather than an overrun: + +- **`open_epoch` and epoch state.** Folded into `submit_spend` and + `settle_epoch`, which need none: a spend records its own timestamp and + settlement reads the clock. Four instructions instead of seven. +- **`claim_reward` and dwell rewards.** Cut to a flat entry fee, as the cut line + above anticipated. The fees accrue on the pool account and no instruction pays + them out, which is a known loose end rather than a feature. +- **`self_spend`.** Not built because it is not needed: a member relays for + themselves at zero fee and settles their own batch after the timeout. Same + exit, one fewer instruction, less attack surface. Pinned by + `a_member_can_always_exit_without_any_relay`. +- **The `audit` command.** Shipped as `analyze`. +- **"The measurement is load-bearing."** Withdrawn rather than descoped. A + program cannot check funding provenance, so the on-chain `k_floor` bounds + program-visible membership only and the measurement lives beside it. Claiming + otherwise would have been exactly the marketing line this plan disavows. +- **Mainnet.** Deliberately not deployed: the setup is reproducible rather than + secure, and a live pool with public toxic waste would invite deposits it cannot + protect. The README states the reasoning. + +`EFFECTIVE_K` shipped as `docs/PROVENANCE_METHOD.md`. + ### Stretch, only if the above is complete A composability contribution to `account-cooker`, funding an agent fleet through diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 479787f8..0ff6ddde 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -67,9 +67,18 @@ controls where its users' money came from. What we do instead: It is *reproducible*, which is a different and lesser property. The seed is public, so the toxic waste is public, so **proofs are forgeable by anyone who -runs the setup**. `mirror verify-setup` lets a third party re-derive the deployed -key and confirm it matches this circuit — that is what reproducibility buys, and -it is worth having, but it is not security. +runs the setup**. + +`mirror verify-setup` is what reproducibility buys. It re-derives the key from the +public seed and this circuit and compares it **element by element** — alpha, beta, +gamma, delta and every IC point — against the key compiled into the program. The +digest it should print is + + b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7 + +Checking the whole key matters: a verifier that compared only `delta`, as one +competing ceremony does, would certify a key belonging to an entirely different +circuit. Reproducibility is worth having and it is not security. Production needs a multi-party ceremony. The scaffolding for one is not in this submission and we do not claim it is. diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index 9344d334..b2b79efe 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -341,8 +341,12 @@ fn a_real_proof_verifies_on_chain_and_records_the_spend() { "the relay that submitted must be the one paid at settlement" ); - // No member key appears anywhere in this path. - assert_ne!(relay.pubkey(), beneficiary); + // No member key appears anywhere in this path. The spend was submitted by a + // relay and the record names only the relay and the beneficiary, neither of + // which is the depositor: every deposit in this pool came from its own fresh + // keypair inside `seeded_pool`, and none of those signed anything here. + assert_eq!(record.relay(), relay.pubkey().to_bytes()); + assert_ne!(record.relay(), record.beneficiary()); } #[test] @@ -409,6 +413,10 @@ fn a_relay_cannot_redirect_the_payout() { .send(ix, &relay) .expect_err("a redirected payout was accepted"); println!("redirect rejected: {err}"); + assert!( + err.contains("Custom(18)"), + "expected ProofVerificationFailed: {err}" + ); } #[test] @@ -449,6 +457,10 @@ fn a_relay_cannot_inflate_its_own_fee() { .send(ix, &relay) .expect_err("an inflated relay fee was accepted"); println!("fee inflation rejected: {err}"); + assert!( + err.contains("Custom(18)"), + "expected ProofVerificationFailed: {err}" + ); } #[test] @@ -469,6 +481,7 @@ fn a_proof_against_an_unknown_root_is_rejected() { .send(ix, &relay) .expect_err("an unknown root was accepted"); println!("unknown root rejected: {err}"); + assert!(err.contains("Custom(17)"), "expected UnknownRoot: {err}"); } #[test] @@ -487,6 +500,10 @@ fn a_pool_below_its_anonymity_floor_refuses_to_act() { .send(ix, &relay) .expect_err("a spend below the anonymity floor was accepted"); println!("below-floor rejected: {err}"); + assert!( + err.contains("Custom(19)"), + "expected BelowAnonymityFloor: {err}" + ); } #[test] @@ -506,6 +523,10 @@ fn a_forged_proof_is_rejected() { .send(ix, &relay) .expect_err("a corrupted proof was accepted"); println!("forged proof rejected: {err}"); + assert!( + err.contains("Custom(18)"), + "expected ProofVerificationFailed: {err}" + ); } // --------------------------------------------------------------------------- @@ -759,6 +780,7 @@ fn settlement_refuses_a_beneficiary_the_proof_did_not_bind() { .send(ix, &settler) .expect_err("settlement paid an unbound beneficiary"); println!("unbound beneficiary rejected: {err}"); + assert!(err.contains("Custom(2)"), "expected InvalidPda: {err}"); } /// The permissionless exit, demonstrated rather than added. @@ -1135,6 +1157,66 @@ fn a_settler_cannot_change_how_many_accounts_an_action_gets() { .send(ix, &settler) .expect_err("settlement invoked with fewer accounts than declared"); println!("truncated action account list rejected: {err}"); + // The one negative case without a code of ours: the runtime runs out of + // accounts before the program can rule on it. Asserted explicitly so the + // exception stays visible rather than looking like an oversight. + assert!( + err.contains("NotEnoughAccountKeys"), + "expected the runtime to refuse before the program: {err}" + ); +} + +/// The vault can never be one of the callee's accounts, and a settler that tries +/// is refused rather than left to fail deeper in the runtime. THREAT_MODEL.md +/// states this; without a test it was the one claim there resting on reading. +#[test] +fn a_settler_cannot_place_the_vault_in_an_action_account_list() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let memo: Pubkey = MEMO_PROGRAM.parse().unwrap(); + env.svm.add_program(memo, &memo_bytes()).unwrap(); + + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let payload = b"vault please".as_slice(); + + let proof = action_proof_n(&keys, &tree, ¬es, 2, &memo, &beneficiary, 1, payload); + let nullifier = proof.public_inputs[1]; + let ix = action_ix_n( + &env, + &proof, + nullifier, + &memo, + &beneficiary, + &relay.pubkey(), + 1, + payload, + ); + env.send(ix, &relay).expect("submitting"); + + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let mut clock = env.svm.get_sysvar::(); + clock.unix_timestamp += mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS + 1; + env.svm.set_sysvar(&clock); + + let batch = vec![(spend_pda, beneficiary, relay.insecure_clone())]; + let ix = settle_ix_full( + &env, + &batch, + &settler.pubkey(), + &[Some(memo)], + &[vec![AccountMeta::new(env.vault, false)]], + ); + let err = env + .send(ix, &settler) + .expect_err("the vault was accepted as an action account"); + println!("vault as action account rejected: {err}"); + assert!( + err.contains("Custom(1)"), + "expected MalformedInstruction: {err}" + ); } #[test] @@ -1286,6 +1368,10 @@ fn a_relay_cannot_declare_an_account_count_the_member_did_not_authorise() { .send(ix, &relay) .expect_err("a relay inflated the account count and burnt the note"); println!("inflated action_accounts rejected: {err}"); + assert!( + err.contains("Custom(1)"), + "expected MalformedInstruction: {err}" + ); // The nullifier must still be spendable: the griefing attempt cost the // member nothing. From 0b256b12d86446d25f73850e14406d0abd17bbde Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 20:15:53 -0300 Subject: [PATCH 29/83] docs: record all three measurement runs, including the two that produced nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run 3 is the first clean census: 84 members attempted against a live pool's depositors, 40 resolved, zero RPC failures. Loss factor 0.1219, inside an unresolved bracket of 0.0253 to 0.1837. The tool still declines to call that a result, on two gates. Under half the members reached a class, so the bracket's readings differ more than sevenfold and either quoted alone would describe the sampling budget rather than the pool. And Chao1 estimates 108 provenance classes against 17 observed, so most of the distribution was never seen. Both are stated beside the number rather than under it. Run 2 is logged as the failure it was, because a log that keeps only successful runs is selecting rather than reporting. It refused at a 14.94% failure rate, and the cause is worth recording: HTTP 429 arriving at 0.7 requests per second, far under any documented limit, because providers meter by compute units and a thousand-signature page is expensive in those terms. Lowering the request rate — the obvious response — would not have helped. The cause was a page cap of 60, set on my mistaken belief that paging deeper would resolve more chains. It would not have: the volume-hub rule needs about eight pages to clear its threshold and estimate an age, and the other fifty-two were waste that spent the budget which made the endpoint refuse. Run 3 uses a fifth of the calls and resolves nearly as many members. That diagnosis was only possible after instrumenting the collector, which had been collapsing every failure to RpcFailure with no cause — honest in its census and undiagnosable in practice. --- README.md | 7 +- crates/mirror-cli/src/main.rs | 6 +- crates/mirror-provenance/src/rpc.rs | 16 +- crates/mirror-provenance/src/trace.rs | 25 +- data/sample-privacycash.json | 1255 +++++++------------------ docs/MEASUREMENT_LOG.md | 90 +- 6 files changed, 455 insertions(+), 944 deletions(-) diff --git a/README.md b/README.md index 9d7f3407..774166da 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,12 @@ So this submission claims exactly two things: action's on-chain funding trace leads to the pool and is identical for every member. 2. **The membership side is measured**, from real mainnet data, with the method - and its limits published beside the number. + and its limits published beside the number. Measured on a live pool's + depositors: `ρ = 0.1219`, inside an unresolved bracket of `0.0253 … 0.1837`, + from a clean census with zero infrastructure failures — and the tool declines + to call that a result, because 40 of 84 members reached a class and two-thirds + of the class distribution was never observed. `docs/MEASUREMENT_LOG.md` has + every run, including the two that produced nothing. Anything we cannot support with a measurement whose method is published, we do not say. There is a section below of things we deliberately do not claim. diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index f9fffb2a..c6c14c74 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -334,7 +334,7 @@ fn main() -> Result<()> { // started throttling. let elapsed = started.elapsed().as_secs_f64().max(0.001); eprintln!( - " [{:>3}/{}] {}… {} hops, {:?} ({} calls, {:.1}/s, {:.0}s elapsed)", + " [{:>3}/{}] {}… {} hops, {:?} ({} calls, {:.1}/s, {:.0}s elapsed){}", p.done, p.total, &p.seed[..p.seed.len().min(8)], @@ -343,6 +343,10 @@ fn main() -> Result<()> { p.rpc_calls, p.rpc_calls as f64 / elapsed, elapsed, + p.error + .as_deref() + .map(|e| format!("\n -> {}", &e[..e.len().min(160)])) + .unwrap_or_default(), ); }); diff --git a/crates/mirror-provenance/src/rpc.rs b/crates/mirror-provenance/src/rpc.rs index a4711a39..6f81505a 100644 --- a/crates/mirror-provenance/src/rpc.rs +++ b/crates/mirror-provenance/src/rpc.rs @@ -98,7 +98,11 @@ impl RpcClient { RpcClient { endpoint: endpoint.into(), agent: ureq::AgentBuilder::new() - .timeout(Duration::from_secs(30)) + . // A full signature page is a large response, and the slowest ones on a + // busy address exceeded thirty seconds — which arrived as a + // transport failure and was counted against the run rather than + // retried. + timeout(Duration::from_secs(90)) .build(), min_interval: interval, last_call: None, @@ -147,9 +151,13 @@ impl RpcClient { "params": params, }); - let mut wait = std::time::Duration::from_millis(800); + // Providers meter by compute units, not request count, and a full + // signature page is expensive in those terms — so a 429 arrives even at + // well under one request per second, and the budget needs seconds to + // refill rather than milliseconds. + let mut wait = std::time::Duration::from_secs(2); let mut response: Option = None; - for attempt in 0..6 { + for attempt in 0..8 { self.pace(); self.calls += 1; match self.agent.post(&self.endpoint).send_json(body.clone()) { @@ -157,7 +165,7 @@ impl RpcClient { response = Some(r.into_json().map_err(|e| RpcError::Shape(e.to_string()))?); break; } - Err(ureq::Error::Status(429 | 502 | 503, _)) if attempt < 5 => { + Err(ureq::Error::Status(429 | 502 | 503, _)) if attempt < 7 => { std::thread::sleep(wait); wait *= 2; } diff --git a/crates/mirror-provenance/src/trace.rs b/crates/mirror-provenance/src/trace.rs index 8e5f0a02..e662d48b 100644 --- a/crates/mirror-provenance/src/trace.rs +++ b/crates/mirror-provenance/src/trace.rs @@ -59,6 +59,14 @@ pub struct CollectionConfig { /// Credits below this are not treated as funding. pub min_edge_lamports: u64, /// Signature pages to read before declaring the address high-activity. + /// + /// Small on purpose. Paging exists to find the birth edge, and for an + /// address busy enough not to reach it, what we actually need is only enough + /// history to clear the volume-hub threshold and estimate an age — a few + /// thousand signatures, not tens of thousands. Raising this buys almost + /// nothing and costs a great deal: providers meter by compute units, and a + /// deep page walk over busy funders is what makes an endpoint start + /// refusing. pub sig_page_cap: u32, pub page_size: u32, } @@ -114,7 +122,7 @@ impl ChainStop { /// /// A callback rather than printing: a library that writes to stdout takes a /// decision that belongs to whoever is calling it. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone)] pub struct Progress<'a> { pub done: usize, pub total: usize, @@ -123,6 +131,13 @@ pub struct Progress<'a> { pub stop: ChainStop, /// RPC calls made across the whole run so far. pub rpc_calls: u64, + /// When the chain stopped on a failure, what the endpoint actually said. + /// + /// Collapsing every failure to "RpcFailure" made the census honest and the + /// run undiagnosable: a rate limit, a timeout on a large page and a + /// malformed response are three different problems with three different + /// fixes, and they looked identical. + pub error: Option, } /// One member's provenance chain, seed first. @@ -217,6 +232,7 @@ pub struct Collector<'a> { /// make the trace believe every seed was already done and skip the paging /// entirely — which yields a run where nothing resolves and nothing errors. fully_observed: std::collections::BTreeSet, + last_error: Option, ambiguous: u64, edges_seen: u64, } @@ -228,6 +244,7 @@ impl<'a> Collector<'a> { config, facts: BTreeMap::new(), fully_observed: std::collections::BTreeSet::new(), + last_error: None, ambiguous: 0, edges_seen: 0, } @@ -275,6 +292,7 @@ impl<'a> Collector<'a> { hops: chain.visited.len(), stop: chain.stop, rpc_calls: self.client.calls_made(), + error: self.last_error.take(), }); chains.push(chain); } @@ -331,7 +349,10 @@ impl<'a> Collector<'a> { visited.push(current.clone()); let stop = match self.observe(¤t) { - Err(_) => Some(ChainStop::RpcFailure), + Err(e) => { + self.last_error = Some(e.to_string()); + Some(ChainStop::RpcFailure) + } Ok(local_terminal) if local_terminal => Some(ChainStop::LocalTerminal), Ok(_) => None, }; diff --git a/data/sample-privacycash.json b/data/sample-privacycash.json index d4c1d4d2..e4061bba 100644 --- a/data/sample-privacycash.json +++ b/data/sample-privacycash.json @@ -3,26 +3,29 @@ "endpoint": "https://solana-mainnet.g.alchemy.com", "first_available_block": 0, "archival_probe_ok": true, - "collected_at": 1785004247, + "collected_at": 1785013559, "config": { "scope": "Sol", "depth_max": 8, "min_edge_lamports": 500000, - "sig_page_cap": 60, + "sig_page_cap": 8, "page_size": 1000 }, "thresholds_hub_signatures": 5000, "thresholds_distributor_fanout": 20, "thresholds_cluster_min_size": 3, - "rpc_calls": 4852, + "rpc_calls": 1029, "excluded_non_wallet": [ "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp", + "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB", "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY", "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv", "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis", "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX", + "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG", "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS", "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA", @@ -30,7 +33,7 @@ "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp", "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH" ], - "ambiguous_attribution_rate": 0.21428571428571427 + "ambiguous_attribution_rate": 0.2222222222222222 }, "chains": [ { @@ -39,7 +42,7 @@ "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", @@ -61,7 +64,7 @@ "visited": [ "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", @@ -94,7 +97,7 @@ "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", @@ -132,7 +135,7 @@ "visited": [ "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v" ], - "stop": "NoIncomingEdge" + "stop": "PageCapHit" }, { "seed": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", @@ -150,22 +153,7 @@ "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" ], - "stop": "RpcFailure" - }, - { - "seed": "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", - "visited": [ - "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", - "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ", - "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C", - "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", - "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB", - "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte", - "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an", - "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf", - "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM" - ], - "stop": "DepthExceeded" + "stop": "PageCapHit" }, { "seed": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", @@ -180,7 +168,7 @@ "visited": [ "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf" ], - "stop": "NoIncomingEdge" + "stop": "PageCapHit" }, { "seed": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", @@ -207,11 +195,7 @@ "seed": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", "visited": [ "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", - "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", - "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", - "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", - "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", - "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" ], "stop": "PageCapHit" }, @@ -226,16 +210,14 @@ { "seed": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", "visited": [ - "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", - "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165" ], "stop": "PageCapHit" }, { "seed": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", "visited": [ - "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw" ], "stop": "PageCapHit" }, @@ -250,17 +232,7 @@ { "seed": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", "visited": [ - "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", - "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", - "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" - ], - "stop": "RpcFailure" - }, - { - "seed": "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", - "visited": [ - "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu" ], "stop": "PageCapHit" }, @@ -305,7 +277,7 @@ "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", @@ -341,11 +313,9 @@ { "seed": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", "visited": [ - "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", - "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", - "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz" ], - "stop": "NoIncomingEdge" + "stop": "PageCapHit" }, { "seed": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", @@ -358,8 +328,7 @@ { "seed": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", "visited": [ - "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr" ], "stop": "PageCapHit" }, @@ -383,17 +352,10 @@ { "seed": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", "visited": [ - "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV" - ], - "stop": "RpcFailure" - }, - { - "seed": "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", - "visited": [ - "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", - "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q" + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" ], - "stop": "NoIncomingEdge" + "stop": "PageCapHit" }, { "seed": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", @@ -417,7 +379,7 @@ "visited": [ "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", @@ -442,7 +404,7 @@ "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" ], - "stop": "NoIncomingEdge" + "stop": "PageCapHit" }, { "seed": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", @@ -458,7 +420,7 @@ "visited": [ "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", @@ -488,13 +450,9 @@ "seed": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", "visited": [ "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", - "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", - "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", - "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", - "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", - "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" ], - "stop": "NoIncomingEdge" + "stop": "PageCapHit" }, { "seed": "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", @@ -525,7 +483,7 @@ "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", @@ -534,14 +492,12 @@ "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", "visited": [ - "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", - "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", - "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW" ], "stop": "PageCapHit" }, @@ -556,8 +512,7 @@ { "seed": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", "visited": [ - "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", - "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16" ], "stop": "PageCapHit" }, @@ -652,8 +607,7 @@ { "seed": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", "visited": [ - "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX" ], "stop": "PageCapHit" }, @@ -684,8 +638,7 @@ { "seed": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", "visited": [ - "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ" ], "stop": "PageCapHit" }, @@ -724,9 +677,7 @@ { "seed": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", "visited": [ - "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", - "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY" ], "stop": "PageCapHit" }, @@ -736,7 +687,7 @@ "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", @@ -761,7 +712,7 @@ "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", @@ -770,14 +721,14 @@ "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" ], - "stop": "RpcFailure" + "stop": "PageCapHit" }, { "seed": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", "visited": [ "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM" ], - "stop": "RpcFailure" + "stop": "PageCapHit" } ], "facts": { @@ -861,9 +812,9 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1784282864, + "first_seen": 1784916184, "birth_edge": null, "fee_payers": [], "funded": [ @@ -932,32 +883,6 @@ ], "funded": [] }, - "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X": { - "address": "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 4 - }, - "first_seen": 1743262736, - "birth_edge": { - "sink": "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", - "sources": [ - "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" - ], - "value": 779470319, - "signature": "2YjKzdqoKhck9hGc8VmXR5gxdnX1SPDG6WByy1Mm3tHz5rzESsiEf3TLLof8but3XH3XqMVX9ZHbvAR9fhVPanWM", - "slot": 329976683, - "block_time": 1743262736, - "ambiguous_attribution": false - }, - "fee_payers": [ - "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" - ], - "funded": [ - "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu" - ] - }, "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V": { "address": "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", "owner": null, @@ -989,7 +914,7 @@ "owner": null, "executable": false, "signatures": { - "Exact": 1574 + "Exact": 1580 }, "first_seen": 1784752160, "birth_edge": { @@ -1065,13 +990,11 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 32562 + "AtLeast": 8000 }, - "first_seen": 1729284419, + "first_seen": 1781989038, "birth_edge": null, - "fee_payers": [ - "gpoo1atPkrKnfxQ4Qt214ErbgBBJeiksL1EjqBHynbo" - ], + "fee_payers": [], "funded": [] }, "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz": { @@ -1079,9 +1002,9 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1756855300, + "first_seen": 1778370573, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1093,9 +1016,9 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1782422249, + "first_seen": 1784567461, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1257,9 +1180,9 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1775469990, + "first_seen": 1783493331, "birth_edge": null, "fee_payers": [], "funded": [ @@ -1354,55 +1277,16 @@ ], "funded": [] }, - "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9": { - "address": "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", - "owner": null, - "executable": false, - "signatures": { - "Exact": 42 - }, - "first_seen": 1682118364, - "birth_edge": { - "sink": "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", - "sources": [ - "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" - ], - "value": 11742708040, - "signature": "4fDKVyLL5y4Kzuedc8MGwhkkXd1oWbkMqBZTsHtc1H7NFTWijug7Lciv42HG6VkxfjZSemzGn6fCysTTxQPG2Zsb", - "slot": 189742975, - "block_time": 1682118364, - "ambiguous_attribution": false - }, - "fee_payers": [ - "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" - ], - "funded": [ - "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" - ] - }, "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr": { "address": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 9783 - }, - "first_seen": 1781265894, - "birth_edge": { - "sink": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", - "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" - ], - "value": 209692102518, - "signature": "2WnT866FtZdcXqRvfQoypXGhWSQRtqa4cCg453yjkGSuZA9fs8LJviugPbbGP9GYu3TDJhohtJeDwq1AuoeqJBFi", - "slot": 425977688, - "block_time": 1781265894, - "ambiguous_attribution": true + "AtLeast": 8000 }, - "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" - ], + "first_seen": 1781571364, + "birth_edge": null, + "fee_payers": [], "funded": [] }, "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16": { @@ -1410,23 +1294,11 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 9762 - }, - "first_seen": 1772531584, - "birth_edge": { - "sink": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", - "sources": [ - "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" - ], - "value": 302066170, - "signature": "5A3SzsgcHRLJQUVBqDNdCW7M8XnW6jS7TBjiuERozS7a5YAXc4EieN5d58j5Ts7mqNssUsZZj3TKUTNAett3gqgK", - "slot": 403914286, - "block_time": 1772531584, - "ambiguous_attribution": false + "AtLeast": 8000 }, - "fee_payers": [ - "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" - ], + "first_seen": 1772633492, + "birth_edge": null, + "fee_payers": [], "funded": [] }, "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w": { @@ -1507,30 +1379,18 @@ ], "funded": [] }, - "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte": { - "address": "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte", - "owner": null, + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs": { + "address": "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs", + "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 2 - }, - "first_seen": 1784839342, - "birth_edge": { - "sink": "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte", - "sources": [ - "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an" - ], - "value": 2277601434, - "signature": "y43VQ2DHo3WkeWopaAYCfp95qN2cpZnwP5mstF1f8UopGZyJAwuaSYcrCyjg3xt8j15CyMxMn6aFztyYLBkjKHV", - "slot": 434785674, - "block_time": 1784839342, - "ambiguous_attribution": false + "AtLeast": 8000 }, - "fee_payers": [ - "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an" - ], + "first_seen": 1664905134, + "birth_edge": null, + "fee_payers": [], "funded": [ - "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB" + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z" ] }, "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ": { @@ -1562,24 +1422,11 @@ "owner": null, "executable": false, "signatures": { - "Exact": 11 - }, - "first_seen": 1784836721, - "birth_edge": { - "sink": "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", - "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" - ], - "value": 5496619887, - "signature": "XXqgPe1F8j4D89jD1dvd4qP8LDpxJgxyZ7yvGTR6cpNAPz8tfgeypBdBFVUrrBVFwRiAqVq9Sv51b4hYUVWDdM2", - "slot": 434779463, - "block_time": 1784836721, - "ambiguous_attribution": true + "Exact": 0 }, - "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" - ], + "first_seen": null, + "birth_edge": null, + "fee_payers": [], "funded": [] }, "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr": { @@ -1620,12 +1467,48 @@ "fee_payers": [], "funded": [] }, + "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t": { + "address": "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 8000 + }, + "first_seen": 1643476251, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV": { + "address": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5586 + }, + "first_seen": 1774645255, + "birth_edge": { + "sink": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "sources": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "value": 500000000, + "signature": "3wVtJf5fiq7g5cSb8ynKPJswdRx5RQ7GV1oPF1BMiFYKpAkf6T4ZdEbrnfEMQR3DLusyex4JCeZ8aSr9xxYkiuuq", + "slot": 409285335, + "block_time": 1774645255, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "funded": [] + }, "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za": { "address": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 515 + "Exact": 616 }, "first_seen": 1784934334, "birth_edge": { @@ -1693,6 +1576,20 @@ ], "funded": [] }, + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs": { + "address": "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 8000 + }, + "first_seen": 1784665413, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk" + ] + }, "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV": { "address": "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV", "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", @@ -1712,13 +1609,12 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1784762565, + "first_seen": 1784983367, "birth_edge": null, "fee_payers": [], "funded": [ - "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", @@ -1730,23 +1626,11 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 15633 - }, - "first_seen": 1739951269, - "birth_edge": { - "sink": "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", - "sources": [ - "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" - ], - "value": 3000000000, - "signature": "2GUhQXYjQ56ULyk5kFQ5pfduXNtqXhiikEMcLeZBaVDu2zrQfTDkt9bpVV6ETHzmmXaJn7oyGxu73eK9FqtRTF3g", - "slot": 321645004, - "block_time": 1739951269, - "ambiguous_attribution": false + "AtLeast": 8000 }, - "fee_payers": [ - "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" - ], + "first_seen": 1756724646, + "birth_edge": null, + "fee_payers": [], "funded": [ "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF" ] @@ -1756,23 +1640,11 @@ "owner": null, "executable": false, "signatures": { - "Exact": 5 - }, - "first_seen": 1784924107, - "birth_edge": { - "sink": "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", - "sources": [ - "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ" - ], - "value": 2234728114, - "signature": "2KUUfihEsLasy4LETawwKcw9pk4c5yNPhirb3kSWrsRcvitubDvSpAL9Mr4CFjKbM4qT6g3FjHx69zwE6L6Rh1qG", - "slot": 434987740, - "block_time": 1784924107, - "ambiguous_attribution": false + "Exact": 0 }, - "fee_payers": [ - "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ" - ], + "first_seen": null, + "birth_edge": null, + "fee_payers": [], "funded": [] }, "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk": { @@ -1870,23 +1742,11 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 19394 - }, - "first_seen": 1774644767, - "birth_edge": { - "sink": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", - "sources": [ - "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv" - ], - "value": 5972990500, - "signature": "2FGgEReXPwdXR3S6e4R5wxsotsqmfJU3EEC2QmGDtQF5WxGAspSae7s8GWY2FGx4o9u1oyWSsWMrPznZdTD7KNkn", - "slot": 409284086, - "block_time": 1774644767, - "ambiguous_attribution": false + "AtLeast": 8000 }, - "fee_payers": [ - "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv" - ], + "first_seen": 1780423820, + "birth_edge": null, + "fee_payers": [], "funded": [] }, "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB": { @@ -1965,6 +1825,20 @@ ], "funded": [] }, + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE": { + "address": "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 8000 + }, + "first_seen": 1753297373, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem" + ] + }, "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw": { "address": "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", "owner": "11111111111111111111111111111111", @@ -1991,32 +1865,6 @@ "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ" ] }, - "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme": { - "address": "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 7 - }, - "first_seen": 1712133901, - "birth_edge": { - "sink": "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", - "sources": [ - "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" - ], - "value": 981006000, - "signature": "2718Lqzzrche69Pqhrt41GqkDBghW5SrqpxVVjrYV5fDNZ2tdipmhyYVKaXEj7Fex4h5mF5NMJ5QU8ohazEukjZG", - "slot": 258093459, - "block_time": 1712133901, - "ambiguous_attribution": false - }, - "fee_payers": [ - "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" - ], - "funded": [ - "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW" - ] - }, "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF": { "address": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", "owner": "11111111111111111111111111111111", @@ -2046,51 +1894,13 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 11302 - }, - "first_seen": 1728079811, - "birth_edge": { - "sink": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", - "sources": [ - "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" - ], - "value": 200000000, - "signature": "4rvwJT124GYcZAHHJJBqaZ9YRgnxYLF4nQ58wLbJSr1Z9BgH2NXyKvMRRCbN7CUWooZ9BMNPYAACSwPAqq7NgLvt", - "slot": 293725783, - "block_time": 1728079811, - "ambiguous_attribution": false + "AtLeast": 8000 }, - "fee_payers": [ - "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" - ], + "first_seen": 1732758556, + "birth_edge": null, + "fee_payers": [], "funded": [] }, - "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an": { - "address": "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an", - "owner": null, - "executable": false, - "signatures": { - "Exact": 2 - }, - "first_seen": 1784834874, - "birth_edge": { - "sink": "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an", - "sources": [ - "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf" - ], - "value": 2277606434, - "signature": "4Mr6i7VrAqcRcoLitHbNH9CCE2siwMAfUmiZKnc9qBcrawcoa6gU5KwiRUdHP65JbqcdH4szA5EgjRqCCyDtSNJZ", - "slot": 434775083, - "block_time": 1784834874, - "ambiguous_attribution": false - }, - "fee_payers": [ - "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf" - ], - "funded": [ - "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte" - ] - }, "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG": { "address": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", "owner": "11111111111111111111111111111111", @@ -2115,64 +1925,12 @@ ], "funded": [] }, - "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ": { - "address": "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ", + "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG": { + "address": "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG", "owner": null, "executable": false, "signatures": { - "Exact": 2 - }, - "first_seen": 1784924103, - "birth_edge": { - "sink": "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ", - "sources": [ - "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C" - ], - "value": 2234733114, - "signature": "4kpN8LZhHDgAUeceaUxSpUKzCSLaJLBYUVbUWwn7b8TeEzJxwUjmhxRuZs7ggZQiTMFcm1zKXhZ4sSDdzYJcV2eE", - "slot": 434987732, - "block_time": 1784924103, - "ambiguous_attribution": false - }, - "fee_payers": [ - "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C" - ], - "funded": [ - "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7" - ] - }, - "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j": { - "address": "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 72 - }, - "first_seen": 1682118021, - "birth_edge": { - "sink": "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", - "sources": [ - "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" - ], - "value": 11744752320, - "signature": "pALYeH3rq18atGAUHgbqmMBXckY34TfyqKywTwxPzmeg23rXbtbqW9mf3NMct3WsizeZszfuwskkXvbGefvGRRA", - "slot": 189742217, - "block_time": 1682118021, - "ambiguous_attribution": false - }, - "fee_payers": [ - "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" - ], - "funded": [ - "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" - ] - }, - "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG": { - "address": "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG", - "owner": null, - "executable": false, - "signatures": { - "Exact": 0 + "Exact": 0 }, "first_seen": null, "birth_edge": null, @@ -2267,20 +2025,6 @@ "fee_payers": [], "funded": [] }, - "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ": { - "address": "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 60000 - }, - "first_seen": 1685627573, - "birth_edge": null, - "fee_payers": [], - "funded": [ - "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" - ] - }, "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u": { "address": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", "owner": "11111111111111111111111111111111", @@ -2305,6 +2049,20 @@ ], "funded": [] }, + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5": { + "address": "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 8000 + }, + "first_seen": 1784788976, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ" + ] + }, "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7": { "address": "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7", "owner": null, @@ -2369,6 +2127,18 @@ ], "funded": [] }, + "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U": { + "address": "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 8000 + }, + "first_seen": 1784407057, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ": { "address": "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ", "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", @@ -2388,21 +2158,33 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1782513070, + "first_seen": 1784772255, "birth_edge": null, "fee_payers": [], "funded": [ "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC" ] }, + "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E": { + "address": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 8000 + }, + "first_seen": 1750860158, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1": { "address": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 2154 + "Exact": 2156 }, "first_seen": 1684141322, "birth_edge": { @@ -2477,63 +2259,25 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1765047135, + "first_seen": 1780168602, "birth_edge": null, "fee_payers": [], "funded": [ "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" ] }, - "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C": { - "address": "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C", - "owner": null, - "executable": false, - "signatures": { - "Exact": 2 - }, - "first_seen": 1784924100, - "birth_edge": { - "sink": "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C", - "sources": [ - "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4" - ], - "value": 2234738114, - "signature": "C2MVia7U24udidRDGAp7RwyB4WZL2u19NN6pW4URMxVCpPf5HFoxQ7uYnJKRDw8jC3qKxrD1m87iW57h3DiVidt", - "slot": 434987723, - "block_time": 1784924100, - "ambiguous_attribution": false - }, - "fee_payers": [ - "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4" - ], - "funded": [ - "7cED64dW9AkaM84mR5PB8bJrAJHLXZCnEk3y2n5NP5AQ" - ] - }, "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu": { "address": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 20506 + "AtLeast": 8000 }, - "first_seen": 1743263000, - "birth_edge": { - "sink": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", - "sources": [ - "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" - ], - "value": 770000000, - "signature": "rWZNiWUUErqSurTeKbooG5bPJgChg9SvYyYCsgNUy6AXuiHTsyVDYCyzCmYssFrs94PhG2ok9FwYAkVEDrK6efw", - "slot": 329977339, - "block_time": 1743263000, - "ambiguous_attribution": false - }, - "fee_payers": [ - "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" - ], + "first_seen": 1772400244, + "birth_edge": null, + "fee_payers": [], "funded": [] }, "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu": { @@ -2640,9 +2384,9 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1776006611, + "first_seen": 1782119408, "birth_edge": null, "fee_payers": [], "funded": [ @@ -2666,26 +2410,41 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1783634671, + "first_seen": 1784812466, "birth_edge": null, "fee_payers": [], "funded": [ "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL" ] }, + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf": { + "address": "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 8000 + }, + "first_seen": 1782850389, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt" + ] + }, "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW": { "address": "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1777580620, + "first_seen": 1783714097, "birth_edge": null, "fee_payers": [], "funded": [ + "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", @@ -2694,24 +2453,18 @@ "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", - "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", - "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", - "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", - "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", - "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", - "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM" ] @@ -2747,14 +2500,13 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1784138101, + "first_seen": 1784873823, "birth_edge": null, "fee_payers": [], "funded": [ - "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", - "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16" + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" ] }, "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW": { @@ -2783,32 +2535,6 @@ "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq" ] }, - "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM": { - "address": "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM", - "owner": null, - "executable": false, - "signatures": { - "Exact": 2 - }, - "first_seen": 1784834865, - "birth_edge": { - "sink": "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM", - "sources": [ - "7kDQio2u675o7WXC9o9kdzHgiKKHv9E7ysbsNm4JaPvJ" - ], - "value": 2277616434, - "signature": "J5PgLjAoHnCZhNCufCPnDdRDM8jCpkJWtFPW96KXEhdNj417PzakjcuZdzVubT4WU5pL4uPjdfuCERJMe1MZ7HA", - "slot": 434775064, - "block_time": 1784834865, - "ambiguous_attribution": false - }, - "fee_payers": [ - "7kDQio2u675o7WXC9o9kdzHgiKKHv9E7ysbsNm4JaPvJ" - ], - "funded": [ - "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf" - ] - }, "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ": { "address": "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", "owner": "11111111111111111111111111111111", @@ -2840,48 +2566,37 @@ "owner": null, "executable": false, "signatures": { - "Exact": 3 - }, - "first_seen": 1784736131, - "birth_edge": { - "sink": "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", - "sources": [ - "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q" - ], - "value": 350000000, - "signature": "jGbgbqcTo3B2ukJHtnSqyRXGdLfbcVei8RMZPRQFLUkL7X7aa8pA8E3zRpEcYjpF2SYDLApH6FSfCw79eeW9g5w", - "slot": 434539781, - "block_time": 1784736131, - "ambiguous_attribution": false + "Exact": 0 }, - "fee_payers": [ - "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q" - ], + "first_seen": null, + "birth_edge": null, + "fee_payers": [], "funded": [] }, + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk": { + "address": "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 8000 + }, + "first_seen": 1785002821, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD" + ] + }, "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX": { "address": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 16358 + "AtLeast": 8000 }, - "first_seen": 1774739788, - "birth_edge": { - "sink": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", - "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" - ], - "value": 3440555488, - "signature": "3YjE4YpSZT1wXeSk57dn8UrLTMQEWkLqF2r7LpApgEvjMt2h6k5GrkEQfeu4JtsHkPZJvVKqGZkhzKeDxFvroTJj", - "slot": 409527481, - "block_time": 1774739788, - "ambiguous_attribution": true - }, - "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" - ], + "first_seen": 1779008689, + "birth_edge": null, + "fee_payers": [], "funded": [] }, "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ": { @@ -2974,21 +2689,17 @@ "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" ] }, - "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q": { - "address": "BHmSUu88fqQwTfZtGcq3BmjLRJMLy2QWNfjkrPiwCG1q", + "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM": { + "address": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 14 + "AtLeast": 8000 }, - "first_seen": 1784685663, + "first_seen": 1783696023, "birth_edge": null, - "fee_payers": [ - "44XgYLTyNBWQjQeGwz3xY32CmYV2cFi3b2DzEMqEfTaK" - ], - "funded": [ - "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo" - ] + "fee_payers": [], + "funded": [] }, "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm": { "address": "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", @@ -3042,32 +2753,6 @@ "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" ] }, - "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4": { - "address": "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", - "owner": null, - "executable": false, - "signatures": { - "Exact": 4 - }, - "first_seen": 1784839352, - "birth_edge": { - "sink": "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4", - "sources": [ - "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB" - ], - "value": 2277591434, - "signature": "3WBxufbiy1k7yxTPpfzqzXPMiLy1oDBHP12itMUY56ggt3fo7ay49N1m5UZNkLqHPgySwd4NaeTyhuexF37YN1jF", - "slot": 434785697, - "block_time": 1784839352, - "ambiguous_attribution": false - }, - "fee_payers": [ - "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB" - ], - "funded": [ - "8vu5So7Ejtg9ehVUnHsQCJtWpYMM8V5fx4G6zkCo9e5C" - ] - }, "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38": { "address": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", "owner": "11111111111111111111111111111111", @@ -3092,20 +2777,6 @@ ], "funded": [] }, - "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6": { - "address": "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "AtLeast": 60000 - }, - "first_seen": 1783601529, - "birth_edge": null, - "fee_payers": [], - "funded": [ - "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" - ] - }, "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo": { "address": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", "owner": "11111111111111111111111111111111", @@ -3160,7 +2831,7 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 2998 + "Exact": 2999 }, "first_seen": 1780098381, "birth_edge": { @@ -3472,54 +3143,16 @@ "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" ] }, - "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR": { - "address": "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 12754 - }, - "first_seen": 1710763330, - "birth_edge": { - "sink": "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", - "sources": [ - "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" - ], - "value": 1200000000, - "signature": "3BsEYvaLSedjLWzUCzbGUVGT33rwY35m6mzWnXSLit5zTTJ7vctb7oDjhv1mCdUZLWtRHcLMYSxYz5Dp4hhhc5Nw", - "slot": 254924622, - "block_time": 1710763330, - "ambiguous_attribution": false - }, - "fee_payers": [ - "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" - ], - "funded": [ - "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" - ] - }, "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz": { "address": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 12036 + "AtLeast": 8000 }, - "first_seen": 1781801872, - "birth_edge": { - "sink": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", - "sources": [ - "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" - ], - "value": 100000000, - "signature": "2B1U3i2nZKDXancwuyt9UxUa7AJcqgiVyuXzZhECVSZwiy5CKYE7xe5KcEgGymmMS2FcifLQRD7PNgDhhE3zGEkJ", - "slot": 427328708, - "block_time": 1781801872, - "ambiguous_attribution": false - }, - "fee_payers": [ - "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" - ], + "first_seen": 1784436054, + "birth_edge": null, + "fee_payers": [], "funded": [] }, "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU": { @@ -3576,9 +3209,9 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1768133169, + "first_seen": 1781686102, "birth_edge": null, "fee_payers": [], "funded": [] @@ -3588,23 +3221,11 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 18105 + "AtLeast": 8000 }, - "first_seen": 1738500891, - "birth_edge": { - "sink": "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", - "sources": [ - "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" - ], - "value": 29400000000, - "signature": "4ChkDoLctkN81tVBKsZMZVqvdAZViy4zrsKb9K8FiiuD4zvuJnzXq2qjMZAnVNqReUqo7Tb12d3knN7qTDW96mZw", - "slot": 317992649, - "block_time": 1738500891, - "ambiguous_attribution": false - }, - "fee_payers": [ - "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" - ], + "first_seen": 1743113748, + "birth_edge": null, + "fee_payers": [], "funded": [ "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1" ] @@ -3626,9 +3247,9 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1784042134, + "first_seen": 1784043386, "birth_edge": null, "fee_payers": [], "funded": [] @@ -3664,41 +3285,15 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1783491639, + "first_seen": 1784827118, "birth_edge": null, "fee_payers": [], "funded": [ "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9" ] }, - "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K": { - "address": "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", - "owner": null, - "executable": false, - "signatures": { - "Exact": 55 - }, - "first_seen": 1730861242, - "birth_edge": { - "sink": "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", - "sources": [ - "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" - ], - "value": 50000000, - "signature": "BoLFuKgcg38xZSUt14Y5926pkU6LLbaXAKPHPqTzfCKUCYyRNa5T3msnqj7QtwrjP8FUFHxsTKELrjYKA1gUvtX", - "slot": 299749544, - "block_time": 1730861242, - "ambiguous_attribution": false - }, - "fee_payers": [ - "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" - ], - "funded": [ - "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" - ] - }, "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq": { "address": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", "owner": "11111111111111111111111111111111", @@ -3752,7 +3347,7 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 521 + "Exact": 531 }, "first_seen": 1778972678, "birth_edge": { @@ -3915,6 +3510,21 @@ "fee_payers": [], "funded": [] }, + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe": { + "address": "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 8000 + }, + "first_seen": 1785013251, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ" + ] + }, "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL": { "address": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", "owner": "11111111111111111111111111111111", @@ -4118,22 +3728,6 @@ "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" ] }, - "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN": { - "address": "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 18203 - }, - "first_seen": 1712861701, - "birth_edge": null, - "fee_payers": [ - "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J" - ], - "funded": [ - "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" - ] - }, "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7": { "address": "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", "owner": "11111111111111111111111111111111", @@ -4201,112 +3795,44 @@ "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx" ] }, - "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv": { - "address": "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", - "owner": null, - "executable": false, - "signatures": { - "Exact": 2 - }, - "first_seen": 1774644756, - "birth_edge": { - "sink": "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", - "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" - ], - "value": 5973000000, - "signature": "5fTttEt3Pb3F4Sj5dKKrkqFCZEJqR37qyLLYVSA7GwBqbyn4MCv2LDEZBKuh9pGRhdxTJa7xNMKH6c53gXrno7RZ", - "slot": 409284059, - "block_time": 1774644756, - "ambiguous_attribution": true - }, - "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" - ], - "funded": [ - "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY" - ] - }, - "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF": { - "address": "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF", - "owner": null, - "executable": false, - "signatures": { - "Exact": 253 - }, - "first_seen": 1730807547, - "birth_edge": null, - "fee_payers": [ - "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP" - ], - "funded": [ - "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" - ] - }, "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw": { "address": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 14354 - }, - "first_seen": 1777091838, - "birth_edge": { - "sink": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", - "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" - ], - "value": 18571768434, - "signature": "44VV33bxUiT3rDmG3dtN2uxnDx4uBs6keq7PWCmx3rdqWhvPmPXJhrA7Sq7atABrqAkpaBZH5wdRRWMkFQ7j8kNA", - "slot": 415493545, - "block_time": 1777091838, - "ambiguous_attribution": true + "AtLeast": 8000 }, - "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" - ], + "first_seen": 1781124441, + "birth_edge": null, + "fee_payers": [], "funded": [] }, - "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs": { - "address": "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G": { + "address": "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 7899 - }, - "first_seen": 1731918662, - "birth_edge": { - "sink": "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", - "sources": [ - "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" - ], - "value": 11000000000, - "signature": "4NrJoknq1Jx9gtAQ8bxTuhWoNxMWggLDQS4XBN1zsNRVXGfCvC21JBkKGsxLH9hppZBa6KxaMLve5iiowk75DVLb", - "slot": 302103526, - "block_time": 1731918662, - "ambiguous_attribution": false + "AtLeast": 8000 }, - "fee_payers": [ - "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" - ], + "first_seen": 1782676910, + "birth_edge": null, + "fee_payers": [], "funded": [ - "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg" ] }, - "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G": { - "address": "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G", + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE": { + "address": "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1778083481, + "first_seen": 1784920545, "birth_edge": null, "fee_payers": [], "funded": [ - "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg" + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV" ] }, "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT": { @@ -4358,38 +3884,12 @@ ], "funded": [] }, - "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB": { - "address": "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB", - "owner": null, - "executable": false, - "signatures": { - "Exact": 2 - }, - "first_seen": 1784839349, - "birth_edge": { - "sink": "GVk53SLB4iVcsBmdm3SWeo86i6t5TpkXoApoz3jxB7dB", - "sources": [ - "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte" - ], - "value": 2277596434, - "signature": "4tkrNcftx61jVaqP4oWYQUrtx9Lf9pMcX8thrArBUUGBgaNV2EfEXAKr717VfdED71dBpdBcDP1TSAZ46uu96grf", - "slot": 434785691, - "block_time": 1784839349, - "ambiguous_attribution": false - }, - "fee_payers": [ - "56BHpz9S66mBaAzFwaasY98eNw2PvepzUpFSDpbCemte" - ], - "funded": [ - "BU6qqvJ4Lr9rb6QiPyhs9cr6aUc69MKsGPr63n2WVmj4" - ] - }, "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC": { "address": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 291 + "Exact": 292 }, "first_seen": 1783054209, "birth_edge": { @@ -4527,9 +4027,9 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1784328875, + "first_seen": 1784920243, "birth_edge": null, "fee_payers": [], "funded": [ @@ -4541,24 +4041,11 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 18873 - }, - "first_seen": 1780029667, - "birth_edge": { - "sink": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", - "sources": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", - "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" - ], - "value": 2442413156, - "signature": "2UXxvAd3GMF7t1ZMWhrXJcFP3q6iYYcZBEMeynG6PWWz8vrttyjdwXo1KSxrYhGU8zchaaM6979gnU1mz4d3dHKr", - "slot": 422867536, - "block_time": 1780029667, - "ambiguous_attribution": true + "AtLeast": 8000 }, - "fee_payers": [ - "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" - ], + "first_seen": 1783482692, + "birth_edge": null, + "fee_payers": [], "funded": [] }, "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe": { @@ -4566,7 +4053,7 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 712 + "Exact": 724 }, "first_seen": 1784227892, "birth_edge": { @@ -4640,51 +4127,13 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 34631 + "AtLeast": 8000 }, - "first_seen": 1773695393, - "birth_edge": { - "sink": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", - "sources": [ - "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" - ], - "value": 985566110, - "signature": "4bmfxXv69FjWNEcanuh66h7megBdqernEszUDCEWAf8aND6HTdhfQwo1avroBocetPWkg7iybW8kbGo3aJVK5sux", - "slot": 406873448, - "block_time": 1773695393, - "ambiguous_attribution": false - }, - "fee_payers": [ - "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" - ], + "first_seen": 1777633794, + "birth_edge": null, + "fee_payers": [], "funded": [] }, - "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf": { - "address": "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf", - "owner": null, - "executable": false, - "signatures": { - "Exact": 2 - }, - "first_seen": 1784834868, - "birth_edge": { - "sink": "Hhy7vgBizrVFxGpc17dJPkzg7g9me3CpeKFqBrUoehLf", - "sources": [ - "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM" - ], - "value": 2277611434, - "signature": "4aML9ertmZGFiMCfYQ1UKr1RVxg1u7v8CgRBAwG96wwghohzUqDkNJ6Bhfftmjonh6qAqrB9UNUsb6Lw4CsNC6ya", - "slot": 434775071, - "block_time": 1784834868, - "ambiguous_attribution": false - }, - "fee_payers": [ - "Ad5bsZPGb43Gi4ftYuRdipZqmMwxfwoKjr4bLD2xP1cM" - ], - "funded": [ - "7PwJ2JTmyVCMMpdnMrH1LHdccHFKgowoJuasys7de4an" - ] - }, "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9": { "address": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", "owner": "11111111111111111111111111111111", @@ -4738,13 +4187,11 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "Exact": 8835 + "AtLeast": 8000 }, - "first_seen": 1769181912, + "first_seen": 1770644440, "birth_edge": null, - "fee_payers": [ - "BidtDwH9WbEvm9T6tqqsEgAscTzLJeX6H2rGbw22jR39" - ], + "fee_payers": [], "funded": [] }, "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ": { @@ -4800,9 +4247,9 @@ "owner": "11111111111111111111111111111111", "executable": false, "signatures": { - "AtLeast": 60000 + "AtLeast": 8000 }, - "first_seen": 1784207994, + "first_seen": 1784908428, "birth_edge": null, "fee_payers": [], "funded": [ @@ -4866,69 +4313,15 @@ "owner": null, "executable": false, "signatures": { - "Exact": 9987 + "AtLeast": 8000 }, - "first_seen": 1753767232, + "first_seen": 1757619786, "birth_edge": null, - "fee_payers": [ - "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r" - ], + "fee_payers": [], "funded": [ "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE" ] }, - "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf": { - "address": "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", - "owner": null, - "executable": false, - "signatures": { - "Exact": 2651 - }, - "first_seen": 1737914051, - "birth_edge": { - "sink": "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", - "sources": [ - "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" - ], - "value": 50000000000, - "signature": "2rpBPJocSrWKt2G59e29e8Tj7MJDUAQLEKx2GaoAApyUBLbynToft6iDKYZKTWr2BL9UhfiwV7qr6E1exQn6mnAP", - "slot": 316536578, - "block_time": 1737914051, - "ambiguous_attribution": false - }, - "fee_payers": [ - "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" - ], - "funded": [ - "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" - ] - }, - "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe": { - "address": "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", - "owner": "11111111111111111111111111111111", - "executable": false, - "signatures": { - "Exact": 1974 - }, - "first_seen": 1762263640, - "birth_edge": { - "sink": "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", - "sources": [ - "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" - ], - "value": 10000000, - "signature": "2abx2yUwotMytWNz2EAiW6RTrAsz9otzWMXKPyVN8FjtaEa3hp4MTJYi2C3R8XrnUND8MyuMJ5T2yKEb4prkrBVJ", - "slot": 377877154, - "block_time": 1762263640, - "ambiguous_attribution": false - }, - "fee_payers": [ - "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" - ], - "funded": [ - "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz" - ] - }, "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6": { "address": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", "owner": "11111111111111111111111111111111", diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index 2429b957..fa4a2f38 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -67,10 +67,90 @@ anything was wrong. --- -## Run 2 — planned +## Run 2 — 2026-07-25, member-weighted frame, refused on failure rate -**Frame.** Depositors of a live pool, enumerated from its deposit instructions, -each address counted once regardless of how often it transacts. +**Frame.** 100 depositors of Privacy Cash (`9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD`), +enumerated by `mirror seeds` from the program's own deposit transactions and +strided across signature pages so the sample spans the pool's history rather than +its most recent minute. 13 excluded as non-wallets before tracing; 87 attempted. -This is the member-weighted frame, and it is the only one from which an -effective-k for that pool means anything. +**Endpoint.** Alchemy, archival probes passed. Depth 8, page cap 60. 4,852 calls. + +``` +attempted 87 | resolved 47 | evidence-unresolved 8 | budget-unresolved 19 + | scope-unresolved 0 | rpc failures 13 (14.94%) +``` + +**No headline.** The failure rate is 14.94%, so `analyze` refused. This was the +frame working and the endpoint not: 47 of 87 members reached a class, against +zero under Run 1's transaction-weighted frame. + +**What the failures actually were.** The collector reported every failure as +`RpcFailure` with no cause, which made the census honest and the run +undiagnosable. Instrumenting it showed HTTP 429 arriving at **0.7 requests per +second** — far below any documented rate limit. Providers meter by compute units, +not request count, and a 1,000-signature page is expensive in those terms. So the +cause was not the request rate, and lowering it would not have helped; the cause +was a page cap of 60, set on the mistaken belief that paging deeper would resolve +more chains. + +It would not have. The volume-hub rule needs enough history to clear its +threshold and estimate an age — about eight pages. The other fifty-two were +waste that bought nothing and spent the budget that made the endpoint refuse. + +## Run 3 — 2026-07-25, clean census, honest bracket + +**Frame.** The same 100 depositors. 16 excluded as non-wallets; 84 attempted. + +**Endpoint.** Alchemy, archival probes passed. Depth 8, **page cap 8**. 1,029 +calls — a fifth of Run 2 — at 1.5 requests per second. + +``` +attempted 84 | resolved 40 | evidence-unresolved 5 | budget-unresolved 39 + | scope-unresolved 0 | rpc failures 0 (0.00%) +``` + +**Zero infrastructure failures.** Nothing in the unresolved bucket is ours. + +| quantity | value | +|---|---| +| resolved members | 40 | +| provenance classes | 17 | +| **loss factor ρ (point)** | **0.1219** | +| **ρ (unresolved bracket)** | **0.0253 … 0.1837** | +| effective-k, Shannon | 4.88 | +| effective-k, min-entropy | 2.35 | +| Shannon leakage | 3.04 bits | +| Good–Turing coverage | 0.65 | +| Chao1 richness | 108 classes, against 17 observed | + +**The tool declines to call this a result, and it is right.** Two gates fire: + +*Not informative.* 40 of 84 members reached a class — just under half. The +bracket's two readings, unresolved merged into one class versus split into +singletons, differ by more than sevenfold. Quoting either alone would describe +the sampling budget rather than the pool. + +*Under-sampled.* Chao1 estimates 108 provenance classes against 17 observed, and +Good–Turing coverage is 0.65. Most of the class distribution was never seen, and +effective-k measured at small k understates the steady-state loss and does not +extrapolate upward. + +**What this run does establish.** The pipeline works end to end against live +mainnet with a clean census; the member-weighted frame resolves a near-majority +where the transaction-weighted one resolved nothing; and the honesty machinery is +load-bearing rather than decorative — it refused three consecutive runs, twice on +the failure gate and once on the bracket, and each refusal was correct. + +**What a publishable ρ would need.** More resolution, not more members: 39 of the +44 unresolved are budget outcomes, chains that ran out of depth or paging before +reaching a class. That is a bigger call budget spent on depth rather than on +sample size, which is a straightforward thing to buy and not something this +submission claims to have bought. + +## What we do not conclude + +Nothing about how private Privacy Cash is. ρ = 0.1219 is a point estimate inside +a bracket that spans an order of magnitude, from a sample whose class +distribution is two-thirds unobserved. The number is published because the method +is published, not because it settles anything. From c3dc3218fc57f6fff674f3e6571041f0ee2d095e Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 21:09:05 -0300 Subject: [PATCH 30/83] fix(program): a bystander could brick any note for the price of rent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every PDA this program creates is derived from public data, and system_instruction::create_account fails outright when the destination already holds lamports. A nullifier is visible in the submit_spend instruction itself, so anyone who saw the transaction — most obviously the relay it was handed to — could send 890,880 lamports to that spend PDA first and make the note permanently unspendable. There is no refund instruction, so the deposit was gone. The same trick against a pool or vault address denied that denomination's pool forever, since pools are unique per denomination. Replaced with the three-step create: top the account up to rent exemption, then allocate and assign under the PDA's own seeds. A squatter can send lamports but cannot allocate or assign without those seeds, so their transfer now only reduces what the legitimate payer owes. The replay guard also keyed on lamports, which had the same flaw in reverse: a stranger funding the PDA made the program report the note as already spent. It now keys on allocated data, because anyone can send lamports to a public address but only this program can allocate it. Two tests, both of which fail against the previous code: a squatter front-running a spend, and a squatter denying pool creation. --- programs/mirror-pool/src/processor.rs | 110 +++++++++++++++-------- programs/mirror-pool/tests/end_to_end.rs | 79 ++++++++++++++++ 2 files changed, 153 insertions(+), 36 deletions(-) diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index ea33fe6a..d4f546a8 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -74,6 +74,52 @@ struct SpendRequest { payload: Vec, } +/// Creates a program-owned account at a PDA, tolerating lamports already sent to +/// it. +/// +/// `system_instruction::create_account` fails outright when the destination +/// holds any lamports, and that is a griefing vector rather than a safeguard: +/// every PDA this program creates is derived from public data. A nullifier is +/// visible in the `submit_spend` instruction, so anyone who sees the transaction +/// — most obviously the relay it was handed to — can send the rent-exempt +/// minimum to that spend PDA first and make the note permanently unspendable for +/// about 0.00089 SOL. The same trick on a pool or vault address prevents that +/// denomination's pool from ever being created. +/// +/// The three-step form is immune: top the account up to rent exemption, then +/// allocate and assign under the PDA's own seeds. A squatter can send lamports +/// but cannot allocate or assign without those seeds, so their deposit only +/// reduces what the legitimate payer owes. +fn create_pda_account<'a>( + payer: &AccountInfo<'a>, + account: &AccountInfo<'a>, + system: &AccountInfo<'a>, + space: usize, + owner: &Pubkey, + seeds: &[&[u8]], +) -> ProgramResult { + let rent = Rent::get()?; + let required = rent.minimum_balance(space); + let held = account.lamports(); + + if held < required { + solana_program::program::invoke( + &system_instruction::transfer(payer.key, account.key, required - held), + &[payer.clone(), account.clone(), system.clone()], + )?; + } + invoke_signed( + &system_instruction::allocate(account.key, space as u64), + &[account.clone(), system.clone()], + &[seeds], + )?; + invoke_signed( + &system_instruction::assign(account.key, owner), + &[account.clone(), system.clone()], + &[seeds], + ) +} + /// Creates the pool and its vault and seeds the accumulator to an empty tree. /// /// Permissionless: the first caller for a denomination creates the crowd @@ -110,36 +156,29 @@ fn init_pool( return Err(MirrorProgramError::InvalidPda.into()); } - let rent = Rent::get()?; let denom_le = denomination.to_le_bytes(); // The pool account carries the accumulator and, above its own rent, the // reward pool that entry fees accrue into. - invoke_signed( - &system_instruction::create_account( - payer.key, - pool_account.key, - rent.minimum_balance(POOL_LEN), - POOL_LEN as u64, - program_id, - ), - &[payer.clone(), pool_account.clone(), system.clone()], - &[&[POOL_SEED, &denom_le, &[pool_bump]]], + create_pda_account( + payer, + pool_account, + system, + POOL_LEN, + program_id, + &[POOL_SEED, &denom_le, &[pool_bump]], )?; // The vault holds escrow only, and carries no data of its own: the // accounting invariant is a statement about its lamports, so anything else // living there would muddy it. - invoke_signed( - &system_instruction::create_account( - payer.key, - vault_account.key, - rent.minimum_balance(0), - 0, - program_id, - ), - &[payer.clone(), vault_account.clone(), system.clone()], - &[&[VAULT_SEED, expected_pool.as_ref(), &[vault_bump]]], + create_pda_account( + payer, + vault_account, + system, + 0, + program_id, + &[VAULT_SEED, expected_pool.as_ref(), &[vault_bump]], )?; let mut data = pool_account.try_borrow_mut_data()?; @@ -307,7 +346,11 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest if *spend_account.key != expected_spend { return Err(MirrorProgramError::InvalidPda.into()); } - if !spend_account.data_is_empty() || spend_account.lamports() > 0 { + // Keyed on allocated data, not on lamports. Anyone can send lamports to a + // PDA derived from a public nullifier; only this program can allocate it. + // Treating a balance as "already spent" would let a bystander brick a note + // for the price of rent exemption. + if !spend_account.data_is_empty() { return Err(MirrorProgramError::NullifierAlreadySpent.into()); } @@ -357,24 +400,19 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest .verify() .map_err(|_| MirrorProgramError::ProofVerificationFailed)?; - let rent = Rent::get()?; - invoke_signed( - &system_instruction::create_account( - relay.key, - spend_account.key, - rent.minimum_balance(spend_len(req.payload.len())), - spend_len(req.payload.len()) as u64, - program_id, - ), - &[relay.clone(), spend_account.clone(), system.clone()], - &[&[ + create_pda_account( + relay, + spend_account, + system, + spend_len(req.payload.len()), + program_id, + &[ SPEND_SEED, pool_account.key.as_ref(), &req.nullifier, &[spend_bump], - ]], - ) - .map_err(|_| MirrorProgramError::NullifierAlreadySpent)?; + ], + )?; let now = solana_program::clock::Clock::get()?.unix_timestamp; let mut spend_data = spend_account.try_borrow_mut_data()?; diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index b2b79efe..3f974f43 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -1443,3 +1443,82 @@ fn an_unknown_selector_is_refused_before_the_nullifier_burns() { "the nullifier burnt for a selector settlement can never execute" ); } + +/// Squatting a PDA must not brick anything. +/// +/// Every address this program creates is derived from public data — a spend PDA +/// from the nullifier, which is visible in the `submit_spend` instruction +/// itself. `create_account` fails when the destination already holds lamports, +/// so a bystander who front-runs the transaction with the rent-exempt minimum +/// would have made the note permanently unspendable for about 0.00089 SOL. The +/// relay the proof was handed to is the obvious candidate. +/// +/// The three-step create is immune: a squatter can send lamports but cannot +/// allocate or assign without the PDA's seeds, so their deposit only reduces +/// what the legitimate payer owes. +#[test] +fn squatting_a_spend_pda_does_not_brick_the_note() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + let proof = proof_for(&keys, &tree, ¬es, 0, &beneficiary); + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + + // The attacker reads the nullifier and funds the PDA first. + let squatter = Keypair::new(); + env.svm.airdrop(&squatter.pubkey(), 10_000_000_000).unwrap(); + let squat = Instruction::new_with_bytes( + solana_system_interface::program::ID, + &solana_system_interface::instruction::transfer(&squatter.pubkey(), &spend_pda, 890_880) + .data, + vec![ + AccountMeta::new(squatter.pubkey(), true), + AccountMeta::new(spend_pda, false), + ], + ); + env.send(squat, &squatter).expect("squatting the pda"); + assert!( + env.svm.get_account(&spend_pda).unwrap().lamports > 0, + "precondition: the pda is funded by a stranger" + ); + + // The spend must still work. + let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &relay.pubkey()); + env.send(ix, &relay) + .expect("a squatted pda made the note unspendable"); + + let mut data = env.svm.get_account(&spend_pda).unwrap().data; + let record = Spend::load(&mut data).unwrap(); + assert_eq!(record.status(), STATUS_PENDING); + assert_eq!(record.beneficiary(), beneficiary.to_bytes()); +} + +#[test] +fn squatting_a_pool_pda_does_not_prevent_the_pool() { + let mut env = setup(); + let squatter = Keypair::new(); + env.svm.airdrop(&squatter.pubkey(), 10_000_000_000).unwrap(); + + // Deny the canonical pool for this denomination, for the price of rent. + for target in [env.pool, env.vault] { + let squat = Instruction::new_with_bytes( + solana_system_interface::program::ID, + &solana_system_interface::instruction::transfer(&squatter.pubkey(), &target, 890_880) + .data, + vec![ + AccountMeta::new(squatter.pubkey(), true), + AccountMeta::new(target, false), + ], + ); + env.send(squat, &squatter).expect("squatting"); + } + + env.init_pool(); + let mut data = env.pool_state(); + let pool = Pool::load(&mut data).unwrap(); + assert_eq!(pool.denomination(), DENOMINATION); + assert_eq!(pool.k_floor(), K_FLOOR); +} From 2706b21b07ed04ed5dc0b720bd5978f6e184e79d Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 22:51:50 -0300 Subject: [PATCH 31/83] docs: pre-register Run 4's budget before the run can see its result Run 3 missed the informativeness gate by two members. A gate that close is where tuning-until-it-opens is most tempting and least detectable, so the budget is declared and committed before collection starts: same frame, depth 8 to 16, page cap 8 to 24, and a stopping rule that forbids retrying a refusal into a result. The prediction is recorded too, including the part that says the under-sampling flag probably still fires. --- docs/MEASUREMENT_LOG.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index fa4a2f38..22be16d0 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -148,6 +148,41 @@ reaching a class. That is a bigger call budget spent on depth rather than on sample size, which is a straightforward thing to buy and not something this submission claims to have bought. +## Run 4 — pre-registered before collection, 2026-07-25 + +This section was written and committed **before the run started**, so the budget +below is a declaration and not a description. Run 3 missed the informativeness +gate by two members — 40 resolved against a threshold of 42 — and a gate that +close is exactly where the temptation to tune until it opens is strongest. +Declaring the budget in advance and publishing whatever comes out is what makes +this a fourth measurement rather than a search for a fourth answer. + +**Frame.** Unchanged: the same 100 Privacy Cash depositors as Runs 2 and 3, from +`data/seeds-privacycash.txt`. Not re-drawn, not extended. Changing the frame and +the budget together would make the two effects impossible to separate. + +**What changes, and only this.** Run 3 left 39 members in the budget bucket — +chains that ran out of depth or pages before reaching a class. That is the one +bucket a bigger budget can move. So: + +| parameter | Run 3 | Run 4 | +|---|---|---| +| depth | 8 | 16 | +| signature page cap | 8 | 24 | +| endpoint | Alchemy | Helius | +| request rate | 1.5/s | 5/s | + +**Prediction, recorded in advance.** The budget bucket shrinks and resolution +clears the 42-member gate. The under-sampling flag most likely still fires: +Good–Turing coverage was 0.65 and Chao1 estimated 108 classes against 17 +observed, and resolving more members raises coverage slowly. A publishable ρ +carrying an explicit sampling caveat is the expected outcome, not a clean one. + +**Stopping rule.** This is the last run against this frame. Whatever it returns +is what this document reports — including a refusal, and including a ρ that sits +worse for the argument than Run 3's. If it fails on the RPC gate, that is +recorded as a failed run and not retried into success. + ## What we do not conclude Nothing about how private Privacy Cash is. ρ = 0.1219 is a point estimate inside From 061095142a5187c7f3e434cbd6252642d0e445c5 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 23:06:41 -0300 Subject: [PATCH 32/83] measure: Run 4 clears the informativeness gate, and the tail gets worse 50 of 84 members resolved against a threshold of 42, so this is the first run that produces a headline: rho = 0.1032 inside 0.0316 .. 0.1318, a bracket 4.2x wide where Run 3's was 7.3x. Zero RPC failures across 1,613 calls. The pre-registered prediction held on both counts, including the one that was inconvenient. Good-Turing coverage fell from 0.65 to 0.62 and Chao1 richness rose from 108 classes to 193 -- resolving ten more members made the sample look less complete, because the new members landed in new singleton classes rather than in the observed ones. That reclassifies the under-sampling from a caveat into a finding: it is a property of the provenance distribution, not of our budget. There is no call count at which this frame becomes well-observed. Both sample artifacts stay committed so either run is recomputable offline. --- README.md | 33 +- data/sample-privacycash-run4.json | 5285 +++++++++++++++++++++++++++++ docs/MEASUREMENT_LOG.md | 54 + 3 files changed, 5361 insertions(+), 11 deletions(-) create mode 100644 data/sample-privacycash-run4.json diff --git a/README.md b/README.md index 774166da..b1fc92e4 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,18 @@ So this submission claims exactly two things: member. 2. **The membership side is measured**, from real mainnet data, with the method and its limits published beside the number. Measured on a live pool's - depositors: `ρ = 0.1219`, inside an unresolved bracket of `0.0253 … 0.1837`, - from a clean census with zero infrastructure failures — and the tool declines - to call that a result, because 40 of 84 members reached a class and two-thirds - of the class distribution was never observed. `docs/MEASUREMENT_LOG.md` has - every run, including the two that produced nothing. + depositors: **`ρ = 0.1032`**, inside an unresolved bracket of + `0.0316 … 0.1318`, from a clean census with zero infrastructure failures — + 50 of 84 members reached a provenance class. Knowing a member's funding class + costs that pool roughly an order of magnitude of its nominal anonymity. + + The sample's class distribution is heavy-tailed and two-thirds unobserved, + and that is reported as a finding rather than hidden as a caveat: raising the + traversal budget between runs resolved ten more members and made coverage + *worse*, because the new members landed in new singleton classes rather than + in the observed ones. There is no budget at which this distribution becomes + well-observed. `docs/MEASUREMENT_LOG.md` has every run, including the two + that produced nothing, and Run 4's budget was committed before it ran. Anything we cannot support with a measurement whose method is published, we do not say. There is a section below of things we deliberately do not claim. @@ -50,7 +57,7 @@ not say. There is a section below of things we deliberately do not claim. | `crates/mirror-provenance` | The funding-provenance measurement. | | `crates/mirror-cli` | `setup`, `verify-setup`, `check-endpoint`, `seeds`, `collect`, `analyze`, `soak`. | -**179 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**182 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -110,11 +117,15 @@ mirror collect --seeds seeds.txt # the only networked step; writes sample.jso mirror analyze --sample sample.json # pure, offline, deterministic ``` -`data/sample-privacycash.json` is the committed artifact. Anyone holding it -recomputes the result without RPC access and without trusting that our endpoint -behaved the same way on their machine — including the fact that this collection -came back above the 1% RPC-failure limit and so yields no headline. -`docs/MEASUREMENT_LOG.md` records every run. +`data/sample-privacycash-run4.json` is the committed artifact behind the +headline, and `data/sample-privacycash.json` is the earlier run it is compared +against. Both are committed, so anyone holding them recomputes the numbers +without RPC access and without trusting that our endpoint behaved the same way +on their machine — including the run that came back above the 1% RPC-failure +limit and so yielded no headline at all. `docs/MEASUREMENT_LOG.md` records every +run, and Run 4's budget and predictions were committed to git *before* the +collection started, so the parameters are a declaration rather than a +description. ### Design choices that exist to avoid specific published defects diff --git a/data/sample-privacycash-run4.json b/data/sample-privacycash-run4.json new file mode 100644 index 00000000..1897d488 --- /dev/null +++ b/data/sample-privacycash-run4.json @@ -0,0 +1,5285 @@ +{ + "manifest": { + "endpoint": "https://mainnet.helius-rpc.com", + "first_available_block": 0, + "archival_probe_ok": true, + "collected_at": 1785030724, + "config": { + "scope": "Sol", + "depth_max": 16, + "min_edge_lamports": 500000, + "sig_page_cap": 24, + "page_size": 1000 + }, + "thresholds_hub_signatures": 5000, + "thresholds_distributor_fanout": 20, + "thresholds_cluster_min_size": 3, + "rpc_calls": 1613, + "excluded_non_wallet": [ + "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp", + "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", + "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB", + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", + "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY", + "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv", + "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis", + "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX", + "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", + "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG", + "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS", + "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA", + "2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha", + "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp", + "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH" + ], + "ambiguous_attribution_rate": 0.2013888888888889 + }, + "chains": [ + { + "seed": "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "visited": [ + "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "visited": [ + "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k", + "visited": [ + "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", + "visited": [ + "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E" + ], + "stop": "PageCapHit" + }, + { + "seed": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "visited": [ + "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x", + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr", + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ", + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP", + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho", + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva", + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE", + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g", + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99", + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT", + "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD" + ], + "stop": "PageCapHit" + }, + { + "seed": "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "visited": [ + "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "visited": [ + "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" + ], + "stop": "PageCapHit" + }, + { + "seed": "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "visited": [ + "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff", + "visited": [ + "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff" + ], + "stop": "PageCapHit" + }, + { + "seed": "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "visited": [ + "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "visited": [ + "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v", + "visited": [ + "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "visited": [ + "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd", + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC", + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "visited": [ + "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" + ], + "stop": "PageCapHit" + }, + { + "seed": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "visited": [ + "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf", + "visited": [ + "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf" + ], + "stop": "PageCapHit" + }, + { + "seed": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", + "visited": [ + "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx", + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K", + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr" + ], + "stop": "PageCapHit" + }, + { + "seed": "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "visited": [ + "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT", + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1", + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "visited": [ + "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "visited": [ + "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "visited": [ + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165" + ], + "stop": "PageCapHit" + }, + { + "seed": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "visited": [ + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", + "visited": [ + "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx" + ], + "stop": "PageCapHit" + }, + { + "seed": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "visited": [ + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" + ], + "stop": "PageCapHit" + }, + { + "seed": "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "visited": [ + "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB" + ], + "stop": "LocalTerminal" + }, + { + "seed": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", + "visited": [ + "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "visited": [ + "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "visited": [ + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "visited": [ + "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "PageCapHit" + }, + { + "seed": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "visited": [ + "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx", + "visited": [ + "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "visited": [ + "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV" + ], + "stop": "LocalTerminal" + }, + { + "seed": "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "visited": [ + "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "visited": [ + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "visited": [ + "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "visited": [ + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", + "visited": [ + "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU", + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi" + ], + "stop": "PageCapHit" + }, + { + "seed": "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "visited": [ + "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "visited": [ + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "stop": "PageCapHit" + }, + { + "seed": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "visited": [ + "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "visited": [ + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U", + "visited": [ + "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U" + ], + "stop": "PageCapHit" + }, + { + "seed": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "visited": [ + "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ" + ], + "stop": "LocalTerminal" + }, + { + "seed": "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "visited": [ + "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "visited": [ + "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "visited": [ + "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH", + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "stop": "PageCapHit" + }, + { + "seed": "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", + "visited": [ + "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT", + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "visited": [ + "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "visited": [ + "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "visited": [ + "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + ], + "stop": "PageCapHit" + }, + { + "seed": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "visited": [ + "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", + "visited": [ + "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "visited": [ + "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "visited": [ + "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr" + ], + "stop": "PageCapHit" + }, + { + "seed": "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "visited": [ + "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" + ], + "stop": "PageCapHit" + }, + { + "seed": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "visited": [ + "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" + ], + "stop": "PageCapHit" + }, + { + "seed": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "visited": [ + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "stop": "PageCapHit" + }, + { + "seed": "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "visited": [ + "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "visited": [ + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "visited": [ + "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe", + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor", + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu", + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S", + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "visited": [ + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "visited": [ + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G" + ], + "stop": "PageCapHit" + }, + { + "seed": "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "visited": [ + "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o" + ], + "stop": "LocalTerminal" + }, + { + "seed": "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "visited": [ + "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "visited": [ + "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "visited": [ + "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "visited": [ + "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "visited": [ + "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "stop": "PageCapHit" + }, + { + "seed": "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "visited": [ + "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "visited": [ + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "visited": [ + "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB", + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx", + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S", + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr", + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh", + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm", + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94", + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf", + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ", + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x", + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha", + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t", + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe", + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH", + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz", + "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ" + ], + "stop": "DepthExceeded" + }, + { + "seed": "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "visited": [ + "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "visited": [ + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "visited": [ + "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B", + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "stop": "PageCapHit" + }, + { + "seed": "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR", + "visited": [ + "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR" + ], + "stop": "PageCapHit" + }, + { + "seed": "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "visited": [ + "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn" + ], + "stop": "PageCapHit" + }, + { + "seed": "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "visited": [ + "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "visited": [ + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "visited": [ + "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "PageCapHit" + }, + { + "seed": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "visited": [ + "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "visited": [ + "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "visited": [ + "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" + ], + "stop": "PageCapHit" + }, + { + "seed": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "visited": [ + "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" + ], + "stop": "PageCapHit" + }, + { + "seed": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", + "visited": [ + "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM" + ], + "stop": "PageCapHit" + } + ], + "facts": { + "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E": { + "address": "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1784615427, + "birth_edge": { + "sink": "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 110900000, + "signature": "5e6e31Ja1kF2DbV7qrcSW9RZYywU7TpJfxr667CtkwBe4toXGeyZnYtdT8NJeQzM1fTpQULLkDXkraeVcYxArg8W", + "slot": 434252353, + "block_time": 1784615427, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [] + }, + "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma": { + "address": "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 607 + }, + "first_seen": 1783375005, + "birth_edge": { + "sink": "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 22913500000, + "signature": "2AkoeVHhVQwaNuxQEya9FmdWcho8YDPBUExnQwQ51LZCGAN81Xa6gmUiq5KufZD7VNapVbL566cGNQ54cHcQGDAZ", + "slot": 431247217, + "block_time": 1783375005, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z": { + "address": "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4277 + }, + "first_seen": 1647049347, + "birth_edge": { + "sink": "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", + "sources": [ + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" + ], + "value": 7244953950, + "signature": "419TeAWehDaugVDXtfT9GochWU2uzZfYE8Mj9KcRLxDtwZpVies6nT8ujSsqz5xcah5gEALomEeyeL5igrt1ZFeG", + "slot": 124535935, + "block_time": 1647049347, + "ambiguous_attribution": false + }, + "fee_payers": [ + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" + ], + "funded": [ + "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H" + ] + }, + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm": { + "address": "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784753164, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH" + ] + }, + "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ": { + "address": "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 35 + }, + "first_seen": 1783722353, + "birth_edge": { + "sink": "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 12948500000, + "signature": "5rMkvoDiSrN16YHjCLekrebvkmsHxvssazWRbP2Fq5rZNhyyMcUTULMb136XBMrKVZzwRyhbj7qb3cx3sgGiWAoX", + "slot": 432098751, + "block_time": 1783722353, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha": { + "address": "2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9": { + "address": "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784475307, + "birth_edge": { + "sink": "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "sources": [ + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn", + "6tckHFBpiJ8YgYN8FUskvtvTpXQZ55g5LHeo1kvELoDQ" + ], + "value": 5010000000, + "signature": "4bs1AqpjFmhSM6Hgg7nGwN7FbytYnLRcGmiTMP6TCCRP2rhhT4hf8Bb62Jm7uTqxc4vs9scPgT3vbEb98uQfdNF3", + "slot": 433922248, + "block_time": 1784475307, + "ambiguous_attribution": true + }, + "fee_payers": [ + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn" + ], + "funded": [] + }, + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X": { + "address": "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1743262736, + "birth_edge": { + "sink": "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", + "sources": [ + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" + ], + "value": 779470319, + "signature": "2YjKzdqoKhck9hGc8VmXR5gxdnX1SPDG6WByy1Mm3tHz5rzESsiEf3TLLof8but3XH3XqMVX9ZHbvAR9fhVPanWM", + "slot": 329976683, + "block_time": 1743262736, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" + ], + "funded": [ + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu" + ] + }, + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V": { + "address": "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", + "owner": null, + "executable": false, + "signatures": { + "Exact": 249 + }, + "first_seen": 1776070891, + "birth_edge": { + "sink": "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", + "sources": [ + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ" + ], + "value": 600000000, + "signature": "2ifPXJE5e8zqjQTqTP7duLXvDyDjTsJrqcf5iEdywn3Z29p3Zsr3RcgYFXPkkxZMhMNg3HQyeVHSqQHhCrDAYnPM", + "slot": 412912638, + "block_time": 1776070891, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ" + ], + "funded": [ + "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU" + ] + }, + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH": { + "address": "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894159, + "birth_edge": { + "sink": "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH", + "sources": [ + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz" + ], + "value": 849144919, + "signature": "4YP76aBoEjaNaxf1e6Va5x4ZZLNVfo6vNpY8kzqQcquQZWaGeiiJgrKaPTrP1udmGV8Kj82jNeBCsH8PzcwBPk9b", + "slot": 394707612, + "block_time": 1768894159, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz" + ], + "funded": [ + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe" + ] + }, + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy": { + "address": "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1593 + }, + "first_seen": 1784752160, + "birth_edge": { + "sink": "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "sources": [ + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" + ], + "value": 43881516584, + "signature": "3NEwDfuC73NVthq8zeasPw9Ud6qcWZiy65wFFZSWcbxP86UjAsc9ZPApAdjk7EYo88ptZcUZbSLdmQwZkgqSCcRc", + "slot": 434577818, + "block_time": 1784752160, + "ambiguous_attribution": false + }, + "fee_payers": [ + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" + ], + "funded": [ + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x" + ] + }, + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg": { + "address": "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12 + }, + "first_seen": 1781285770, + "birth_edge": { + "sink": "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "sources": [ + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G" + ], + "value": 1677937000, + "signature": "2TJSuukc2SNr9YL2qRHeNHmu37kfsE2cBnQkz7TJTxuRkVw48fefVYqxsGCV2ULsb1PnVvzmcQGJt5J7uouabgYZ", + "slot": 426027687, + "block_time": 1781285770, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G" + ], + "funded": [] + }, + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem": { + "address": "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", + "owner": null, + "executable": false, + "signatures": { + "Exact": 263 + }, + "first_seen": 1713736580, + "birth_edge": { + "sink": "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", + "sources": [ + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" + ], + "value": 12006763000, + "signature": "62g49CyyWTmom7Qxdnr64iSy7W8PDuxrZojz5q6ztpgYkM7WwwXkfcyeNVditM45iomtRpgqXUbLW8kXpScStkuL", + "slot": 261492401, + "block_time": 1713736580, + "ambiguous_attribution": false + }, + "fee_payers": [ + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" + ], + "funded": [ + "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq" + ] + }, + "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf": { + "address": "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1768518508, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz": { + "address": "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1771845560, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38" + ] + }, + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS": { + "address": "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783787452, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B" + ] + }, + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1": { + "address": "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 100 + }, + "first_seen": 1784818359, + "birth_edge": { + "sink": "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1", + "sources": [ + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7" + ], + "value": 6000000000, + "signature": "2StbhyBVwhP2wRhTYMM31x5qU3B2WBr1jFXx6KD6CGeXp9xpWQqN7RKoxa98X86h8v6SGprqMdi9CVGh9PLSjAQH", + "slot": 434735789, + "block_time": 1784818359, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7" + ], + "funded": [ + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT" + ] + }, + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr": { + "address": "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1784752151, + "birth_edge": { + "sink": "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr", + "sources": [ + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ" + ], + "value": 45278992944, + "signature": "LnCoQVyRCZyi9ktCp3JyZPKhGrTu19FzpmhP7SjvQ8TKYS7uu9dPE23AuzRQz32YWqaTQG9rabCkoAihw2swguS", + "slot": 434577797, + "block_time": 1784752151, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ" + ], + "funded": [ + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" + ] + }, + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT": { + "address": "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1332 + }, + "first_seen": 1632786500, + "birth_edge": { + "sink": "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT", + "sources": [ + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m" + ], + "value": 100000000, + "signature": "2BLRDfM37WhxjZUNXSnPPqSZP3Yq2xTG2BsyUiQWCFaLcqPigMGHgSrX2uijsXVbB9AKL4sYZenPjui5ArFMGjcP", + "slot": 98678981, + "block_time": 1632786500, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m" + ], + "funded": [ + "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t" + ] + }, + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt": { + "address": "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784444104, + "birth_edge": { + "sink": "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", + "sources": [ + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" + ], + "value": 5986856990, + "signature": "3k3C9SKrmZZ9aKwV75WzsqDnXRj1jnrfVoq6W5E4wxyjcAH7ZGiMbrDKVqStRdtpFm4Y2vzYA8WvEyUdSs67b1UN", + "slot": 433847718, + "block_time": 1784444104, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" + ], + "funded": [ + "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH" + ] + }, + "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL": { + "address": "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784544632, + "birth_edge": { + "sink": "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "sources": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "value": 928815670, + "signature": "85PNojTrpgdkLHWP1CQRGD6G6CttqKUCr5zYDjLXkFsB4teH5D9kcZqZjTAWX3ydRqfVhwLAKLzC6zYsU3K1A8L", + "slot": 434085334, + "block_time": 1784544632, + "ambiguous_attribution": false + }, + "fee_payers": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "funded": [] + }, + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe": { + "address": "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894162, + "birth_edge": { + "sink": "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe", + "sources": [ + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH" + ], + "value": 849139919, + "signature": "2R1Goqyiuu2pZC2cXWBUawgSdAFH38yF9ciQW9p9igR6YUwHoKq9iigoHZeQgBq4ZrcmdMkp39jThvqv2bZcevMF", + "slot": 394707621, + "block_time": 1768894162, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH" + ], + "funded": [ + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t" + ] + }, + "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H": { + "address": "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 528 + }, + "first_seen": 1734052375, + "birth_edge": { + "sink": "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "sources": [ + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z" + ], + "value": 1020000000, + "signature": "4Mm83Hp6ciDRNzM3tSFJt6AyoXPc7kD4moyqFobEtqzXDAnC2k5hNt83wNGSsn61KQjaNdyYYqWHcJjaUA9qubwX", + "slot": 307120184, + "block_time": 1734052375, + "ambiguous_attribution": false + }, + "fee_payers": [ + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z" + ], + "funded": [] + }, + "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH": { + "address": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 277 + }, + "first_seen": 1784444332, + "birth_edge": { + "sink": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "sources": [ + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt" + ], + "value": 2985933011, + "signature": "4e1bYdz4z6QTzWFbSbjFRJpDdhuc2m2zaopi6x8N1w81fa6352rDi2VCzqyxKdRuvPKthVeikZXGj91o6bvCfeK4", + "slot": 433848260, + "block_time": 1784444332, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt" + ], + "funded": [] + }, + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr": { + "address": "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1780914830, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh" + ] + }, + "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7": { + "address": "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784462394, + "birth_edge": { + "sink": "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 90000000, + "signature": "4jhAt2GJwyGRfe5sJkFB2HVjnUzLi86PVfucCvUM6u3JFRwegv57f4ZkhmRH6huyS8pNEZa8nbPs9Kb6PtXYUAqz", + "slot": 433891372, + "block_time": 1784462394, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99": { + "address": "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99", + "owner": null, + "executable": false, + "signatures": { + "Exact": 662 + }, + "first_seen": 1783864275, + "birth_edge": { + "sink": "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99", + "sources": [ + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT" + ], + "value": 16330335554, + "signature": "32dUmLfqYokcYLdCcE19JzJ8Y423ssL8GJ6qLEDaZtmWHsQtpTF4e9KVxmSuidihQAcV5cuPpsMZjhWWd8F149TB", + "slot": 432444959, + "block_time": 1783864275, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT" + ], + "funded": [ + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g" + ] + }, + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu": { + "address": "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1764 + }, + "first_seen": 1781821319, + "birth_edge": { + "sink": "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu", + "sources": [ + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ" + ], + "value": 400000000, + "signature": "t439hwXMQP6B4Yk83sUHquHMZR9q8bqQK4na6cWaxbvVCnQKw5hzceNSBXCJKRmcEzmJBFLd19bfcWsSDKGxhU6", + "slot": 427377732, + "block_time": 1781821319, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ" + ], + "funded": [ + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor" + ] + }, + "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv": { + "address": "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze": { + "address": "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 697 + }, + "first_seen": 1784674265, + "birth_edge": { + "sink": "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 6040700317, + "signature": "3tGju22hVCi5Tn5mQv65Uokr3ahGwzYsU5urYkse9Bq4SgSRPmwiU8FzhX51HBdMCKyTor6i8G1uc2LnhHge4uRb", + "slot": 434392522, + "block_time": 1784674265, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9": { + "address": "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", + "owner": null, + "executable": false, + "signatures": { + "Exact": 42 + }, + "first_seen": 1682118364, + "birth_edge": { + "sink": "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", + "sources": [ + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" + ], + "value": 11742708040, + "signature": "4fDKVyLL5y4Kzuedc8MGwhkkXd1oWbkMqBZTsHtc1H7NFTWijug7Lciv42HG6VkxfjZSemzGn6fCysTTxQPG2Zsb", + "slot": 189742975, + "block_time": 1682118364, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" + ], + "funded": [ + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" + ] + }, + "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ": { + "address": "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894142, + "birth_edge": { + "sink": "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ", + "sources": [ + "3LKFtdjyaX773fYgnXShWhicr3ZcNaSWWz2ggQVBgtsC" + ], + "value": 849154919, + "signature": "3fTk4eCJFMtoe3jFJ4XhXYEHqurQ5DHnehPcQoWMQLXmPmQTdns183yrWX7C8YWCvrtV7GPaU1GJ7WZfpdB9KSo1", + "slot": 394707570, + "block_time": 1768894142, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3LKFtdjyaX773fYgnXShWhicr3ZcNaSWWz2ggQVBgtsC" + ], + "funded": [ + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz" + ] + }, + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz": { + "address": "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894156, + "birth_edge": { + "sink": "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz", + "sources": [ + "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ" + ], + "value": 849149919, + "signature": "ebF99xjMkDDNHUYqxCU6fukrRihQpvEHCgpii9dnf6AeBFGPYvk94S2cq7QnfBfhRooK1361Xddiaksk6hnVxQX", + "slot": 394707604, + "block_time": 1768894156, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ" + ], + "funded": [ + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH" + ] + }, + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr": { + "address": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9790 + }, + "first_seen": 1781265894, + "birth_edge": { + "sink": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 209692102518, + "signature": "2WnT866FtZdcXqRvfQoypXGhWSQRtqa4cCg453yjkGSuZA9fs8LJviugPbbGP9GYu3TDJhohtJeDwq1AuoeqJBFi", + "slot": 425977688, + "block_time": 1781265894, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16": { + "address": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9762 + }, + "first_seen": 1772531584, + "birth_edge": { + "sink": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "sources": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "value": 302066170, + "signature": "5A3SzsgcHRLJQUVBqDNdCW7M8XnW6jS7TBjiuERozS7a5YAXc4EieN5d58j5Ts7mqNssUsZZj3TKUTNAett3gqgK", + "slot": 403914286, + "block_time": 1772531584, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "funded": [] + }, + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT": { + "address": "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT", + "owner": null, + "executable": false, + "signatures": { + "Exact": 273 + }, + "first_seen": 1782950807, + "birth_edge": { + "sink": "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT", + "sources": [ + "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD" + ], + "value": 100000000, + "signature": "5xcQPdqkxyx3pQsyF85wNxj7b4TuW3nB3wqryzTVHLtMeDfKsGzjY64jXDCnnfvru4BqEunbdYQzBAHBBJKG3d6V", + "slot": 430196288, + "block_time": 1782950807, + "ambiguous_attribution": false + }, + "fee_payers": [ + "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD" + ], + "funded": [ + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99" + ] + }, + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w": { + "address": "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1783188037, + "birth_edge": { + "sink": "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 100528380, + "signature": "5CdkjWty9v98DDGJPREJK7UpeGUaaBdQxo87kDxyqajMtFtbnzVuL2h9UCu4Ue4iVqu4vWgiBLbs8XCjnAswLrDR", + "slot": 430784309, + "block_time": 1783188037, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [ + "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k" + ] + }, + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk": { + "address": "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1013 + }, + "first_seen": 1781819169, + "birth_edge": { + "sink": "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 25895028000, + "signature": "5e4gkh2xn451jv5rnKUJN81VDUN9bJTY3VESq2eQ7ACZJ51DuXZMFBaat5X5tVe5VBLzSnGbKri4KnpFezdqG3mu", + "slot": 427372317, + "block_time": 1781819169, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S" + ] + }, + "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd": { + "address": "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 133 + }, + "first_seen": 1784917747, + "birth_edge": { + "sink": "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 3063734792, + "signature": "5iF2hZ9Pg77skU74ve2HwpkKpHnSjw2W13DmC67uSo2FAFaUC4FSLEZvv2BSwjwtF1NTfCVuBRKbghGoqpmtUmd7", + "slot": 434972545, + "block_time": 1784917747, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs": { + "address": "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1663976471, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z" + ] + }, + "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ": { + "address": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 17 + }, + "first_seen": 1784823865, + "birth_edge": { + "sink": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "sources": [ + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" + ], + "value": 26325656111, + "signature": "4Fvr5kGDCyhHU3xmpCJvMRy4YAGLs93UAu2mbCG3V39pu9ASyDAqEeMqve3CmwgDUieLGp4q6uoGvFHWBmqt2hvK", + "slot": 434748892, + "block_time": 1784823865, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" + ], + "funded": [] + }, + "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr": { + "address": "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr": { + "address": "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894220, + "birth_edge": { + "sink": "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr", + "sources": [ + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh" + ], + "value": 849094919, + "signature": "4MJQDBRmFT2jFhKZx7rY84rz4GThjUhU2twqk6URjTrCx1k6SQwZd4toBBR6bKnqS7GvPowPP17GnwJw8DGFqviH", + "slot": 394707768, + "block_time": 1768894220, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh" + ], + "funded": [ + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S" + ] + }, + "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS": { + "address": "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t": { + "address": "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9235 + }, + "first_seen": 1638557855, + "birth_edge": { + "sink": "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", + "sources": [ + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT" + ], + "value": 1500000000, + "signature": "54yv3HKUtdYAymSqK7g6Hv3Q3CX9ugxzGJ2SZSF2Mv5oq28tBAwGVdKC6d9NXfbfcxSdkxjaUWson8cdW2Hw2ruH", + "slot": 109908841, + "block_time": 1638557855, + "ambiguous_attribution": false + }, + "fee_payers": [ + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT" + ], + "funded": [] + }, + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV": { + "address": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5586 + }, + "first_seen": 1774645255, + "birth_edge": { + "sink": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "sources": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "value": 500000000, + "signature": "3wVtJf5fiq7g5cSb8ynKPJswdRx5RQ7GV1oPF1BMiFYKpAkf6T4ZdEbrnfEMQR3DLusyex4JCeZ8aSr9xxYkiuuq", + "slot": 409285335, + "block_time": 1774645255, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "funded": [] + }, + "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za": { + "address": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 739 + }, + "first_seen": 1784934334, + "birth_edge": { + "sink": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "sources": [ + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x" + ], + "value": 52977372793, + "signature": "4JY37vAAtVC9a5sQn4z8CY5mRJ5t8njqiBziNK9Ps8QtpG9m1qPsfCLFFo8HuGFjdPNTUtDaLQ9v2TF1auyUDWg7", + "slot": 435012131, + "block_time": 1784934334, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x" + ], + "funded": [] + }, + "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p": { + "address": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1783475831, + "birth_edge": { + "sink": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "sources": [ + "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ" + ], + "value": 388393719000, + "signature": "5imCWUcQWCKj7vDdLUbDXDeE567zN2Eg4Le8AWi6v7f1ZnsbtijSC6i65rA6BpVCsK9kc9fRrBwF4jGBARwWPf1m", + "slot": 431495612, + "block_time": 1783475831, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BrGihgGjCmpu2p96rou6GQj3sQpwgeud937jf4RvZP9G" + ], + "funded": [] + }, + "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3": { + "address": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1784433292, + "birth_edge": { + "sink": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 669228400, + "signature": "66Tc5f3Uko3nDvDySXyoV4rhTBSka9cPczYiReZ8upWdrXKNhRGUHTTsZizm9mdNDEYdTkUmfuynxX3QEPjthemd", + "slot": 433821901, + "block_time": 1784433292, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs": { + "address": "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783951130, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk" + ] + }, + "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV": { + "address": "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u" + ] + }, + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9": { + "address": "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784926254, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW" + ] + }, + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA": { + "address": "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 15633 + }, + "first_seen": 1739951269, + "birth_edge": { + "sink": "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", + "sources": [ + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" + ], + "value": 3000000000, + "signature": "2GUhQXYjQ56ULyk5kFQ5pfduXNtqXhiikEMcLeZBaVDu2zrQfTDkt9bpVV6ETHzmmXaJn7oyGxu73eK9FqtRTF3g", + "slot": 321645004, + "block_time": 1739951269, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" + ], + "funded": [ + "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF" + ] + }, + "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7": { + "address": "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk": { + "address": "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 142 + }, + "first_seen": 1784420501, + "birth_edge": { + "sink": "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1488750000, + "signature": "5reLwzaWen3xeyYobmDJv4AWkboVwkmQea8aqnBXo6AW86tVotCN8aD4HVUseVngB1i8Nz7bWPVayfkXMjq2NZWd", + "slot": 433791379, + "block_time": 1784420501, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S": { + "address": "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894225, + "birth_edge": { + "sink": "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S", + "sources": [ + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr" + ], + "value": 849089919, + "signature": "5mpXMBqiqCUXVfjKe2fqnfr1yXBFt2MnSb5sU4PhDCru1KLnWw7Aji9tVP8ywUoP2X2yAAsgDnLsybmSY33VB9LJ", + "slot": 394707780, + "block_time": 1768894225, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr" + ], + "funded": [ + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx" + ] + }, + "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq": { + "address": "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 54 + }, + "first_seen": 1784676427, + "birth_edge": { + "sink": "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 49804000, + "signature": "2ksPaRqePKa3tLNgb6dfJ4xGxBvHsRJuNXeikfUEuupy8NvcTgf3AL8cq7AZRYALiWDeKz2BWxhNZdDHxdVEoDQg", + "slot": 434397667, + "block_time": 1784676427, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx": { + "address": "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1784813668, + "birth_edge": null, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [] + }, + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY": { + "address": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19394 + }, + "first_seen": 1774644767, + "birth_edge": { + "sink": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "sources": [ + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv" + ], + "value": 5972990500, + "signature": "2FGgEReXPwdXR3S6e4R5wxsotsqmfJU3EEC2QmGDtQF5WxGAspSae7s8GWY2FGx4o9u1oyWSsWMrPznZdTD7KNkn", + "slot": 409284086, + "block_time": 1774644767, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv" + ], + "funded": [] + }, + "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB": { + "address": "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT" + ] + }, + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S": { + "address": "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1310 + }, + "first_seen": 1781821301, + "birth_edge": { + "sink": "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S", + "sources": [ + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk" + ], + "value": 600000000, + "signature": "3B7AsVriGEHPW4TRFBkaFhV6YD4RDZ1w7g4UwNtT7psQb95ZMifH6v4QDUwL1k42tLpqhGqiExaje5LTEMkMBwQ8", + "slot": 427377690, + "block_time": 1781821301, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk" + ], + "funded": [ + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ" + ] + }, + "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB": { + "address": "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz": { + "address": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 963 + }, + "first_seen": 1774473115, + "birth_edge": { + "sink": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 34895500000, + "signature": "3omGYaNeXk9VjMv3rTp9aoaJrkED6b9jTiHoPx4UbRupiWmUn7op8udTBwguzph7YGJWQpNEhnv1FFDskSRsZANh", + "slot": 408844844, + "block_time": 1774473115, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g": { + "address": "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g", + "owner": null, + "executable": false, + "signatures": { + "Exact": 387 + }, + "first_seen": 1783864288, + "birth_edge": { + "sink": "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g", + "sources": [ + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99" + ], + "value": 15184373714, + "signature": "H1qHMYUqhmFjKH3uLfmAM4qZqHch53iJgKFsf93aSE4nfPZujKefZuX633Rn2aSk1PkWTbw341zXYNTC1ZNBTqT", + "slot": 432444990, + "block_time": 1783864288, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99" + ], + "funded": [ + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE" + ] + }, + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE": { + "address": "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1750857325, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem" + ] + }, + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw": { + "address": "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 277 + }, + "first_seen": 1780094619, + "birth_edge": { + "sink": "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 2991500000, + "signature": "84Spr3goYUhDoyS5JCevCc1nDRjo6vWCWeKajzmd3qHWAmsNkBoEjw8322ajWKS5GJyVShK56h1ZUh2VdxtBeCz", + "slot": 423031442, + "block_time": 1780094619, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ" + ] + }, + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme": { + "address": "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1712133901, + "birth_edge": { + "sink": "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", + "sources": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "value": 981006000, + "signature": "2718Lqzzrche69Pqhrt41GqkDBghW5SrqpxVVjrYV5fDNZ2tdipmhyYVKaXEj7Fex4h5mF5NMJ5QU8ohazEukjZG", + "slot": 258093459, + "block_time": 1712133901, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "funded": [ + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW" + ] + }, + "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF": { + "address": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1758995033, + "birth_edge": { + "sink": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "sources": [ + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" + ], + "value": 10000000, + "signature": "5eJNQ4bhKNQYiTVSpUhrHjGpFSs7Ft4GKLrJWqAK5BTnkuzd1xqVV8P1FcpXKG6Zb77U9qdDkyVN6gz1UYzoeB1T", + "slot": 369652410, + "block_time": 1758995033, + "ambiguous_attribution": false + }, + "fee_payers": [ + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" + ], + "funded": [] + }, + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x": { + "address": "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894183, + "birth_edge": { + "sink": "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x", + "sources": [ + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha" + ], + "value": 849124919, + "signature": "4b7V78kfU1AxK6fRrczLLEdLEkMQfF6AurV4jSBMBVLEKstbLTQ65VSPjVyWmyRmz1VuMQNDC7vV1gQDAiLtLdrV", + "slot": 394707672, + "block_time": 1768894183, + "ambiguous_attribution": false + }, + "fee_payers": [ + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha" + ], + "funded": [ + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ" + ] + }, + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW": { + "address": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11302 + }, + "first_seen": 1728079811, + "birth_edge": { + "sink": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "sources": [ + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" + ], + "value": 200000000, + "signature": "4rvwJT124GYcZAHHJJBqaZ9YRgnxYLF4nQ58wLbJSr1Z9BgH2NXyKvMRRCbN7CUWooZ9BMNPYAACSwPAqq7NgLvt", + "slot": 293725783, + "block_time": 1728079811, + "ambiguous_attribution": false + }, + "fee_payers": [ + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" + ], + "funded": [] + }, + "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG": { + "address": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 27 + }, + "first_seen": 1784565160, + "birth_edge": { + "sink": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "sources": [ + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob" + ], + "value": 232000000000, + "signature": "28bt62zYkiihXsM3o7rp2D6YS63G8CpZwUzpqqp2b8jNzGoUih1PWx9FGMdwfQFAEdRGTgz15pPQ8j9uZgDeyvw8", + "slot": 434133197, + "block_time": 1784565160, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob" + ], + "funded": [] + }, + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j": { + "address": "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 72 + }, + "first_seen": 1682118021, + "birth_edge": { + "sink": "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", + "sources": [ + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + ], + "value": 11744752320, + "signature": "pALYeH3rq18atGAUHgbqmMBXckY34TfyqKywTwxPzmeg23rXbtbqW9mf3NMct3WsizeZszfuwskkXvbGefvGRRA", + "slot": 189742217, + "block_time": 1682118021, + "ambiguous_attribution": false + }, + "fee_payers": [ + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + ], + "funded": [ + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" + ] + }, + "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG": { + "address": "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP": { + "address": "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP", + "owner": null, + "executable": false, + "signatures": { + "Exact": 999 + }, + "first_seen": 1784462436, + "birth_edge": { + "sink": "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP", + "sources": [ + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho" + ], + "value": 49648773129, + "signature": "3xd1Te2qtTYHjh2ohyorXsFLgxgWxaveB9tfyGzszJf9MmM1XHhFzQoi5C5ftnUWhXMPRvJpBuHpFidFgZVh2i3g", + "slot": 433891472, + "block_time": 1784462436, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho" + ], + "funded": [ + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm" + ] + }, + "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ": { + "address": "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 32 + }, + "first_seen": 1784389227, + "birth_edge": { + "sink": "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "sources": [ + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb" + ], + "value": 807208726, + "signature": "3vny3LuBCtnjHmNBUw4YGWxGiJdB6MDgCTTwB85ixPF8urpfbhjUiR3DJvptVkoB82k8MLKxgudAvq8m5CPLHCG", + "slot": 433716525, + "block_time": 1784389227, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb" + ], + "funded": [] + }, + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH": { + "address": "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1705 + }, + "first_seen": 1715894645, + "birth_edge": { + "sink": "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH", + "sources": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "value": 106710780, + "signature": "xDgEvEFQw8Qer5vQHtG755NyT2FHXPnFV9CPvhuvZn4NfZ3b5tWrUL5wDcGVyBDVSSA13r7Y6Tgtdp2mjrmHZrH", + "slot": 266193828, + "block_time": 1715894645, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "funded": [ + "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo" + ] + }, + "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH": { + "address": "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ": { + "address": "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1697388134, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" + ] + }, + "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u": { + "address": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784630445, + "birth_edge": { + "sink": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "sources": [ + "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV" + ], + "value": 6831797746, + "signature": "4V6TatwrgYYdAXsbYZ58LgACqMKm51JVfQ6cgiJvjtXwvz8NPjZbhsrz9j2QvwKXXv18bkkgtJScAaw5wZy5sKZk", + "slot": 434288158, + "block_time": 1784630445, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "funded": [] + }, + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5": { + "address": "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784362513, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ" + ] + }, + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7": { + "address": "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7", + "owner": null, + "executable": false, + "signatures": { + "Exact": 176 + }, + "first_seen": 1731698151, + "birth_edge": null, + "fee_payers": [ + "4ritJBeodrzcSDDeAJbKafJniczjYhAfsoZRXTQq9h82" + ], + "funded": [ + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC" + ] + }, + "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT": { + "address": "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784049521, + "birth_edge": { + "sink": "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "sources": [ + "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB" + ], + "value": 2110033428, + "signature": "1tK2a5Q2E19ZBWkhkhMDf36cuv8NZD8pcRFbVhxkR4kedkEjLsxS7dD1QsKHuZ1T77XRbGiWQx9fnQWcDtgLQmD", + "slot": 432895720, + "block_time": 1784049521, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH": { + "address": "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784564457, + "birth_edge": { + "sink": "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 156610510360, + "signature": "3HmDQGGFDNXPquCgrBn4oFueH8EcudAUohYxWiRVekmuX53cZhFotqNnLLPzUEeBnnhHikzZWtUwuP2M6K8EZq1L", + "slot": 434131562, + "block_time": 1784564457, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U": { + "address": "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1782852024, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ": { + "address": "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p" + ] + }, + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx": { + "address": "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784205554, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC" + ] + }, + "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E": { + "address": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1673079363, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1": { + "address": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2157 + }, + "first_seen": 1684141322, + "birth_edge": { + "sink": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "sources": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "value": 90000000, + "signature": "52ZPH7cfAzwauMYh529GgHWhTzRwReNdJyWYr5ZugsAB5gvSmP6CYuCV1U79B9SLxLjk6qy5nsDUmao8Xa5y5EBB", + "slot": 194024003, + "block_time": 1684141322, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "funded": [ + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6" + ] + }, + "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1": { + "address": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5535 + }, + "first_seen": 1747156899, + "birth_edge": { + "sink": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "sources": [ + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" + ], + "value": 1000000000, + "signature": "2cATjnRUeA8sBSjhU4udUTHwqFk5cLQfx2mMZ7isJfKvdfGEJNn4q3Ex72Ww3fRNX7JUQTGpy25R6FVdfFwRP5dV", + "slot": 339786892, + "block_time": 1747156899, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" + ], + "funded": [] + }, + "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb": { + "address": "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1391 + }, + "first_seen": 1778270115, + "birth_edge": { + "sink": "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 31543190000, + "signature": "23eCmHrxuyS6rMmjNNDsJtUZk4vesD5maoraeVjWmqKuLe1yeuELQgEDaqfBDUWSTrkM3iuQFUWAsPpkjAiD6meJ", + "slot": 418463518, + "block_time": 1778270115, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr": { + "address": "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1774935711, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" + ] + }, + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha": { + "address": "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894180, + "birth_edge": { + "sink": "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha", + "sources": [ + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t" + ], + "value": 849129919, + "signature": "z18ostGmqULsMip19y3QrR6sRFfittsoYwNTF1SjPA5iLq1dJfQw8yJvyWKryxBGjhXnhfnVPiuUjpFyggjQj8v", + "slot": 394707664, + "block_time": 1768894180, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t" + ], + "funded": [ + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x" + ] + }, + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu": { + "address": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20517 + }, + "first_seen": 1743263000, + "birth_edge": { + "sink": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "sources": [ + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" + ], + "value": 770000000, + "signature": "rWZNiWUUErqSurTeKbooG5bPJgChg9SvYyYCsgNUy6AXuiHTsyVDYCyzCmYssFrs94PhG2ok9FwYAkVEDrK6efw", + "slot": 329977339, + "block_time": 1743263000, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" + ], + "funded": [] + }, + "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu": { + "address": "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 430 + }, + "first_seen": 1783530592, + "birth_edge": { + "sink": "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "sources": [ + "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o" + ], + "value": 3608900737, + "signature": "39zbFyzcd2pWwLEQBD1gB1qWrjTekKNiicL8TpyjTAoyLABpT518QVo9WVz8yxdzCJF1WiRcvuHzJRR85WFBXjfv", + "slot": 431630423, + "block_time": 1783530592, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn": { + "address": "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784565684, + "birth_edge": { + "sink": "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 841356800, + "signature": "4Y2A99s86tDM9Ef8DQ62ED34YhLejSy9dfkYDHR2rAi6kcnSNvg5pW1F83RyJuHK4DPoxkN5mAkvqvyA1EHJj4Wd", + "slot": 434134426, + "block_time": 1784565684, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [] + }, + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh": { + "address": "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894217, + "birth_edge": { + "sink": "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh", + "sources": [ + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" + ], + "value": 849099919, + "signature": "4UhWatoFQvHLdrg56s4qh3NjJaa9jpsS55YeGbi2FwyNHuoqfnZBLZx3npfoyjhFbvMaNm5vxYmYSAkjPdK2Gioi", + "slot": 394707760, + "block_time": 1768894217, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" + ], + "funded": [ + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr" + ] + }, + "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp": { + "address": "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 205 + }, + "first_seen": 1775579392, + "birth_edge": { + "sink": "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 9131905000, + "signature": "3zWVxUN5PpQhBBHTb7q2iVdGYZWzYX96QFS1nC467YtQBSKuqjZzLFQEPh816DEVyZmtn6xdFvssDpQqdopjccPh", + "slot": 411667852, + "block_time": 1775579392, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi": { + "address": "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1780957321, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU" + ] + }, + "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY": { + "address": "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR": { + "address": "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784446630, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL" + ] + }, + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf": { + "address": "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1778787271, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt" + ] + }, + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW": { + "address": "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1782168865, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", + "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM" + ] + }, + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx": { + "address": "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894233, + "birth_edge": { + "sink": "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx", + "sources": [ + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S" + ], + "value": 849084919, + "signature": "4MQSbWiBDWsVRVVKMjprc9fMfevDFzvAJJDcTd7PPwkGKXTfqJtB5frc9naHWzJv1S5yi3XLnmKZPMEbQx95M7aU", + "slot": 394707800, + "block_time": 1768894233, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S" + ], + "funded": [ + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB" + ] + }, + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ": { + "address": "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784653077, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16" + ] + }, + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW": { + "address": "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW", + "owner": null, + "executable": false, + "signatures": { + "Exact": 711 + }, + "first_seen": 1663704019, + "birth_edge": { + "sink": "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 3079735480, + "signature": "24tTRqCdoMDLxwi41jhddFdaqydjZowSRXZHW2xuBqNttskX1ej72PhQUDqGoiF9jY5u9i8kbdyMcx5MMUrRWXsD", + "slot": 151573078, + "block_time": 1663704019, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq" + ] + }, + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t": { + "address": "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894166, + "birth_edge": { + "sink": "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t", + "sources": [ + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe" + ], + "value": 849134919, + "signature": "2HfqS2otjA4GU4BjEiL2ei8YhTcqcwpSZYh7L5E7UfwfaigJ78sP6qi22gs9zZjcZFk8S3FkSK1tf7Zx5CSAKmYE", + "slot": 394707631, + "block_time": 1768894166, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe" + ], + "funded": [ + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha" + ] + }, + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ": { + "address": "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2412 + }, + "first_seen": 1781821309, + "birth_edge": { + "sink": "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", + "sources": [ + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S" + ], + "value": 500000000, + "signature": "4wVPQe58DmcHwkdVmHuvqWFGnQZhQ4ndiumpcrvNSYx6bL2bBCk6CCNeWpKDPzvc8Y68RpfFKuCrfScRuL2VaE58", + "slot": 427377712, + "block_time": 1781821309, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S" + ], + "funded": [ + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu" + ] + }, + "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo": { + "address": "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk": { + "address": "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784996107, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD" + ] + }, + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX": { + "address": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 16379 + }, + "first_seen": 1774739788, + "birth_edge": { + "sink": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 3440555488, + "signature": "3YjE4YpSZT1wXeSk57dn8UrLTMQEWkLqF2r7LpApgEvjMt2h6k5GrkEQfeu4JtsHkPZJvVKqGZkhzKeDxFvroTJj", + "slot": 409527481, + "block_time": 1774739788, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ": { + "address": "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 107 + }, + "first_seen": 1783458420, + "birth_edge": null, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ": { + "address": "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 618 + }, + "first_seen": 1774728729, + "birth_edge": { + "sink": "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", + "sources": [ + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" + ], + "value": 11650417543, + "signature": "5TnbPcr9zLALAWF82aL7PVBNbX7PkaUTzdXgAWEnvZAeE8n5cYzXhyEKh438w9uj6LrF316rj73McA84g5DHueHM", + "slot": 409499170, + "block_time": 1774728729, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" + ], + "funded": [ + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V" + ] + }, + "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG": { + "address": "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 6 + }, + "first_seen": 1784519630, + "birth_edge": { + "sink": "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "sources": [ + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB" + ], + "value": 104889883544, + "signature": "5162f689Bxmi9PA5Fj9QVxBuxjHrWqbJnoHVVjnmUg4HB9j8XnMvUmLaugVAcyEnAbpiPQNQiukSiE2D2H2AUKaQ", + "slot": 434026767, + "block_time": 1784519630, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB" + ], + "funded": [] + }, + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC": { + "address": "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1748177913, + "birth_edge": { + "sink": "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC", + "sources": [ + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7" + ], + "value": 1029000000, + "signature": "CfG62TFDb4cuNH7FNWz4Cr2Ga14r88NoTXbSDkWmGSDhFTqxdKxinZcyAeSJ33eE1VPiqfwfgcBcmyMRijYEDNb", + "slot": 342369414, + "block_time": 1748177913, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7" + ], + "funded": [ + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" + ] + }, + "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM": { + "address": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783098061, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm": { + "address": "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", + "owner": null, + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1784580094, + "birth_edge": { + "sink": "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", + "sources": [ + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP" + ], + "value": 50607243678, + "signature": "2h4gvshcwfRhkDXQhiLMAWYTWM4xMUnW8iGdJpEnhQRUYZy5DuMQma319GnL8wCprP7MTXGgb8ymBWvQ1jwTwoht", + "slot": 434168171, + "block_time": 1784580094, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP" + ], + "funded": [ + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ" + ] + }, + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m": { + "address": "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3738 + }, + "first_seen": 1632342722, + "birth_edge": { + "sink": "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 2099995000, + "signature": "56mSUdyemWLDwZbWDjZCUhJDHGqgJaGiMGVKSDovzLsM54Lc26JshmZPgz4R3N6Se2B12z8CkeAQGsQz8ELb4XjJ", + "slot": 97853541, + "block_time": 1632342722, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [ + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT" + ] + }, + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K": { + "address": "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 581 + }, + "first_seen": 1775667915, + "birth_edge": { + "sink": "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K", + "sources": [ + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr" + ], + "value": 12399485120, + "signature": "1hqU1oga3ZT4F3uT5iAm3W4zB3eiH9vd2Rop2Rtyva35zubCfiYZCFgAaSaBCD48a3iTz1aL9zpTnZ4JRq6yZE5", + "slot": 411892813, + "block_time": 1775667915, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr" + ], + "funded": [ + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" + ] + }, + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE": { + "address": "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE", + "owner": null, + "executable": false, + "signatures": { + "Exact": 930 + }, + "first_seen": 1784212794, + "birth_edge": { + "sink": "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE", + "sources": [ + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g" + ], + "value": 23308100465, + "signature": "48hdxxJEtMvxPq47QrfdcsDqMDNP8FzBPgM3RN4YfsmFxFrnsRSuNHEdiXcnjYTxujSyVAooQqfo271w8BCoKFVk", + "slot": 433291812, + "block_time": 1784212794, + "ambiguous_attribution": false + }, + "fee_payers": [ + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g" + ], + "funded": [ + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva" + ] + }, + "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38": { + "address": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 567 + }, + "first_seen": 1759849779, + "birth_edge": { + "sink": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "sources": [ + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + ], + "value": 1000000000, + "signature": "55aNfhpFmWyptpJ52kvWWSpaHePRjZLDNbf9kzwSKgUtvvcp4XFhGpKXbU5tVD4wYFh3DrtjJpfnKfLoyGKkadYN", + "slot": 371813843, + "block_time": 1759849779, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + ], + "funded": [] + }, + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6": { + "address": "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784387653, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" + ] + }, + "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo": { + "address": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3597 + }, + "first_seen": 1736334578, + "birth_edge": { + "sink": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "sources": [ + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH" + ], + "value": 170000000000, + "signature": "2MLS8L4uUc8L3nNZpej5csKZ3d5JuNqKGkUjpX1E1ebmqEBzcxBP3ZccC4FvbAgAFWchKPoey8mhH98JCnCxCQ9x", + "slot": 312653033, + "block_time": 1736334578, + "ambiguous_attribution": false + }, + "fee_payers": [ + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH" + ], + "funded": [] + }, + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw": { + "address": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 155 + }, + "first_seen": 1780608496, + "birth_edge": { + "sink": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 3965000, + "signature": "2jPiYFrpAtgu9YXus8V7NPkAWW6xWy5zBdh42P7S5sticdDDVgPGo91ne8yDeLjXhZHyH6zxZnWg69yPXRApwheC", + "slot": 424324895, + "block_time": 1780608496, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ": { + "address": "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2999 + }, + "first_seen": 1780098381, + "birth_edge": { + "sink": "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "sources": [ + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw" + ], + "value": 800000000, + "signature": "3owWqGQL5cNVZcCTN235bbaS2gyez3LxGz663Gej6dtmqBhUyrbhJNfoP1VaVryXN47bdKWoxji34CAu6oUPsxdP", + "slot": 423040927, + "block_time": 1780098381, + "ambiguous_attribution": false + }, + "fee_payers": [ + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw" + ], + "funded": [] + }, + "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P": { + "address": "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 96 + }, + "first_seen": 1784854429, + "birth_edge": { + "sink": "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "sources": [ + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT" + ], + "value": 6000000000, + "signature": "5KA9mkbPhkRXKEhf1FeiRuxHphiMMkRgq3FsNjkKk2Ra7gRMMqmB7V5gEvocEESMeK24LVsUC24gWjBx7EyiX3Kp", + "slot": 434821643, + "block_time": 1784854429, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT" + ], + "funded": [] + }, + "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis": { + "address": "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD": { + "address": "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 627 + }, + "first_seen": 1753997946, + "birth_edge": { + "sink": "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "sources": [ + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" + ], + "value": 123066991, + "signature": "5Rtx3JoWd3XaAGxkckRh6w7aeW4W9M2ZbG8NQt7SDLdunYiy5eHXiF3Kc9Dp8QBMJABzH2T2ctoAzCEZF57ckAAB", + "slot": 357026958, + "block_time": 1753997946, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" + ], + "funded": [] + }, + "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA": { + "address": "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr": { + "address": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784599658, + "birth_edge": { + "sink": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "sources": [ + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + ], + "value": 440000000, + "signature": "4QJkfNCAWjNh2mfYrx9WN6EBe6cCpPUABueLgf4WfqJ5smBUdqbHDcsQXAHT6d8a6q4hFNMvf7jAozFE4jw1rXwj", + "slot": 434214781, + "block_time": 1784599658, + "ambiguous_attribution": false + }, + "fee_payers": [ + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + ], + "funded": [] + }, + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob": { + "address": "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 134 + }, + "first_seen": 1784218241, + "birth_edge": null, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG" + ] + }, + "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh": { + "address": "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784649420, + "birth_edge": { + "sink": "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "sources": [ + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr" + ], + "value": 1850143000, + "signature": "3YgFygisXK1X8kyUXV6LzPEcJ36tKUDkuyo7UvX3KdwULNGDe2wLYiB7KSCVcyM9NejVf2Q4Gr2KV2p11SSvGbCy", + "slot": 434333385, + "block_time": 1784649420, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr" + ], + "funded": [] + }, + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb": { + "address": "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784389224, + "birth_edge": { + "sink": "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 807213726, + "signature": "2MwPJoYeQHsFvpTXyue8m776mx5fBtFDYgY7hcZpTuz641UR5R8xz7yoe7NXT8e1mRojMFpwKs6Pkdk1fFgSe6zr", + "slot": 433716518, + "block_time": 1784389224, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ" + ] + }, + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT": { + "address": "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 87 + }, + "first_seen": 1784838909, + "birth_edge": { + "sink": "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT", + "sources": [ + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1" + ], + "value": 6000000000, + "signature": "3wsTxnCcjxate2zoeFse3mstAwC5KUkvVH4AWjgi9FTKZmwvm7vjJb7N5EPGR5rpcDePeFVXxopoMKiWRsG67bTu", + "slot": 434784661, + "block_time": 1784838909, + "ambiguous_attribution": false + }, + "fee_payers": [ + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1" + ], + "funded": [ + "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P" + ] + }, + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB": { + "address": "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB", + "owner": null, + "executable": false, + "signatures": { + "Exact": 44 + }, + "first_seen": 1768894237, + "birth_edge": { + "sink": "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB", + "sources": [ + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx" + ], + "value": 849079919, + "signature": "ZPtjQoLcno8LwEZaoxwJrAZfMfcHmszyxAA1V2FW6ohJxa3tbczaP1T4s4d5HgxHTxDS7tTjRK5GnV3x55nWPCQ", + "slot": 394707808, + "block_time": 1768894237, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx" + ], + "funded": [ + "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG" + ] + }, + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN": { + "address": "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784377763, + "birth_edge": { + "sink": "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1299793806, + "signature": "wbcYZ4GZzni8ThU986zLwqQUmhcY4gZfGFBbC1ffpv6TwUkWtxPLvUt6FB3fT9DvuaEKu21bsm6RwpSBVgrkiEh", + "slot": 433689096, + "block_time": 1784377763, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9" + ] + }, + "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU": { + "address": "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784657908, + "birth_edge": { + "sink": "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1996965000, + "signature": "5bJGTURSxfgDoe8QBCjFXtRwNcrBHqLyN8oJ3HjztNDMnbgtzNG11DtZ51MgkqAkcii2BWghA9KB3MitvWTQxF5d", + "slot": 434353532, + "block_time": 1784657908, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94": { + "address": "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894203, + "birth_edge": { + "sink": "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94", + "sources": [ + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf" + ], + "value": 849109919, + "signature": "2XXhJExg7yyA6WH34H5Q99ZtCVavER65hC3V7tf6oNm5onx4KDVoDEpSgAaoupvn721NrE7KdSiMQ4cjKZY1UfJy", + "slot": 394707725, + "block_time": 1768894203, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf" + ], + "funded": [ + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" + ] + }, + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR": { + "address": "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12753 + }, + "first_seen": 1710763330, + "birth_edge": { + "sink": "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", + "sources": [ + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" + ], + "value": 1200000000, + "signature": "3BsEYvaLSedjLWzUCzbGUVGT33rwY35m6mzWnXSLit5zTTJ7vctb7oDjhv1mCdUZLWtRHcLMYSxYz5Dp4hhhc5Nw", + "slot": 254924622, + "block_time": 1710763330, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" + ], + "funded": [ + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" + ] + }, + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz": { + "address": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12525 + }, + "first_seen": 1781801872, + "birth_edge": { + "sink": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "sources": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ], + "value": 100000000, + "signature": "2B1U3i2nZKDXancwuyt9UxUa7AJcqgiVyuXzZhECVSZwiy5CKYE7xe5KcEgGymmMS2FcifLQRD7PNgDhhE3zGEkJ", + "slot": 427328708, + "block_time": 1781801872, + "ambiguous_attribution": false + }, + "fee_payers": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ], + "funded": [] + }, + "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU": { + "address": "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2660 + }, + "first_seen": 1777272559, + "birth_edge": { + "sink": "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "sources": [ + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V" + ], + "value": 2500000000, + "signature": "ETzNdupbU3xXhkPGEw9Hn8tPN6NDugSpsVob4iHjzcfzccdcUAisMiYbqkwSbCm6AxsfqxZvhjfCCUC3P1kEHRc", + "slot": 415950310, + "block_time": 1777272559, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V" + ], + "funded": [] + }, + "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U": { + "address": "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 272 + }, + "first_seen": 1757688961, + "birth_edge": { + "sink": "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 93700000, + "signature": "3PeWiircHtHEH9T78i4FKAoUNP3568WEdnJr8PgTPdhPPi958X6NrtthHtGn2r21ovMKKWwCo8TkMtc6rmzw6odX", + "slot": 366350032, + "block_time": 1757688961, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR": { + "address": "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1776160820, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi": { + "address": "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18105 + }, + "first_seen": 1738500891, + "birth_edge": { + "sink": "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", + "sources": [ + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + ], + "value": 29400000000, + "signature": "4ChkDoLctkN81tVBKsZMZVqvdAZViy4zrsKb9K8FiiuD4zvuJnzXq2qjMZAnVNqReUqo7Tb12d3knN7qTDW96mZw", + "slot": 317992649, + "block_time": 1738500891, + "ambiguous_attribution": false + }, + "fee_payers": [ + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + ], + "funded": [ + "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1" + ] + }, + "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX": { + "address": "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff": { + "address": "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784043064, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x": { + "address": "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x", + "owner": null, + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1784934326, + "birth_edge": { + "sink": "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x", + "sources": [ + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" + ], + "value": 54374849153, + "signature": "4Y7SpKFVWPXmK1GJQMsZfuQnxiYyoi7NcJbchUzRemXMGt3DWRUWJBT7L8seY3MymYCvHhSv7XravPdS34KHgmpL", + "slot": 435012112, + "block_time": 1784934326, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" + ], + "funded": [ + "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za" + ] + }, + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn": { + "address": "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784432144, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9" + ] + }, + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K": { + "address": "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", + "owner": null, + "executable": false, + "signatures": { + "Exact": 55 + }, + "first_seen": 1730861242, + "birth_edge": { + "sink": "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", + "sources": [ + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" + ], + "value": 50000000, + "signature": "BoLFuKgcg38xZSUt14Y5926pkU6LLbaXAKPHPqTzfCKUCYyRNa5T3msnqj7QtwrjP8FUFHxsTKELrjYKA1gUvtX", + "slot": 299749544, + "block_time": 1730861242, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" + ], + "funded": [ + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" + ] + }, + "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq": { + "address": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 294 + }, + "first_seen": 1764868637, + "birth_edge": { + "sink": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "sources": [ + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem" + ], + "value": 1390000000, + "signature": "42jLxFrfSKoLo8ZPnaMZfGU56WVo4mrSi1RUEweRK84j2R4vSSRetdHu9HErxgK6eR87R6vt2c7T2C4KUTaDjf4F", + "slot": 384466286, + "block_time": 1764868637, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem" + ], + "funded": [] + }, + "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do": { + "address": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1776858197, + "birth_edge": { + "sink": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", + "sources": [ + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" + ], + "value": 1007615782, + "signature": "2KPM9xqT2mfMAqEZDX51t6o3RgYoLnJZgU6NPzZ57YV14DZy8VsnyYxntqnKbhtYAZwE4puvnjUP9PmE5y4aUBa1", + "slot": 414903289, + "block_time": 1776858197, + "ambiguous_attribution": false + }, + "fee_payers": [ + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" + ], + "funded": [] + }, + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW": { + "address": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 545 + }, + "first_seen": 1778972678, + "birth_edge": { + "sink": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 6632702930, + "signature": "vJcCHLLH6FaWPDarJWTvMdpKckbK9yPGhy9qgUgypRyheTmVdPiEMf5vCioqKT82iF8G7VZ95vXGuYWoECTAh2B", + "slot": 420215149, + "block_time": 1778972678, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr" + ] + }, + "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH": { + "address": "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 369 + }, + "first_seen": 1745361264, + "birth_edge": { + "sink": "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "sources": [ + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn" + ], + "value": 1000000000, + "signature": "5Ugmig4PTLRcf3GTuiY6G47gQn5bxj3UDp4YpKDsvY1JaPwwn1DMU1bQPnTXgrP8wQtrEZfmpLthv8gjADrp7mpu", + "slot": 335243820, + "block_time": 1745361264, + "ambiguous_attribution": false + }, + "fee_payers": [ + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn" + ], + "funded": [] + }, + "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o": { + "address": "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu" + ] + }, + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn": { + "address": "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5289 + }, + "first_seen": 1702336727, + "birth_edge": { + "sink": "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 4372981634, + "signature": "5KeWCHfUdwtMws4e48ZDAqSa2K6bFWeiX4irurLzrFw1geWqtgjHvY1F8T8Cma4n6XuaJ3TbQBaKFZjeLGEK6Wau", + "slot": 235369057, + "block_time": 1702336727, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [ + "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH" + ] + }, + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM": { + "address": "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 157 + }, + "first_seen": 1780522066, + "birth_edge": null, + "fee_payers": [ + "7rtiKSUDLBm59b1SBmD9oajcP8xE64vAGSMbAN5CXy1q" + ], + "funded": [ + "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW" + ] + }, + "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx": { + "address": "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1150 + }, + "first_seen": 1737448881, + "birth_edge": { + "sink": "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "sources": [ + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B" + ], + "value": 672745485, + "signature": "64C1Msn6wiLA7U5x5ZjYhPx2cj813py9Pft9Bc41VYtbtWHinBJefXt3rj5KaWTuvXC7aoYwr3Y8tsfj1zed1ZSw", + "slot": 315391758, + "block_time": 1737448881, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B" + ], + "funded": [] + }, + "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ": { + "address": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 58 + }, + "first_seen": 1783645887, + "birth_edge": { + "sink": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 14433952357, + "signature": "4RbXiaGAmS7WfUmruWn3zBUTwaF2WzVDno2bSTiZFH7cbYSeVeUGgS184pvQaV3ySoRBNGkx6r68M8r5AA5ZbJTo", + "slot": 431912014, + "block_time": 1783645887, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp": { + "address": "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe": { + "address": "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1785023492, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ" + ] + }, + "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL": { + "address": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 89 + }, + "first_seen": 1784429559, + "birth_edge": { + "sink": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1987000000, + "signature": "22tiKy2Uhr5HbzaoUTkf5gTS2VHov1xMY998kBCQbYybACx6Y3fJtxRBaj8ppaMyahAGEgnC5SggnKt6HbxcwNRb", + "slot": 433813000, + "block_time": 1784429559, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B": { + "address": "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 145 + }, + "first_seen": 1769758558, + "birth_edge": { + "sink": "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 193300000, + "signature": "52ygQjkxX6CiTWcaT8KoncyGrbR97uAqzrJ91LySCtkZDCNDa3WTHpnvNSJkRXacynWy4D5j16sFCh1PWGdesPET", + "slot": 396868730, + "block_time": 1769758558, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt": { + "address": "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11 + }, + "first_seen": 1784509948, + "birth_edge": { + "sink": "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "sources": [ + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe" + ], + "value": 14000000000, + "signature": "55y1RvP9p644eWZy6JypDF39fB4u7XCBXYmQhjDRX5AgytWCpsC2J4SiffzCo9gLPHpPoqs6AXrCXyb7mgUooZXD", + "slot": 434004156, + "block_time": 1784509948, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe" + ], + "funded": [] + }, + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm": { + "address": "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894210, + "birth_edge": { + "sink": "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm", + "sources": [ + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94" + ], + "value": 849104919, + "signature": "2ga76DPJVD9CWDYmtpb17BURi9EcLNFxeNFi172x5aRN9es1qAh5HRHf9ZNCzGtADfp9LzzcF2t26MdDn5jEDT5C", + "slot": 394707744, + "block_time": 1768894210, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94" + ], + "funded": [ + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh" + ] + }, + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor": { + "address": "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 856 + }, + "first_seen": 1783108022, + "birth_edge": { + "sink": "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor", + "sources": [ + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu" + ], + "value": 200000000, + "signature": "3XBCwvm2pUrPEp3PCUKm4NiNy7pGhpxqpy2u2eWnMBs5sJgoYk55DPp77RSEn4m2vL9C61sWiefBAiArFGEaUgFw", + "slot": 430586035, + "block_time": 1783108022, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu" + ], + "funded": [ + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe" + ] + }, + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd": { + "address": "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 48 + }, + "first_seen": 1765645486, + "birth_edge": { + "sink": "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd", + "sources": [ + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC" + ], + "value": 88200000, + "signature": "sH5bnyJn2BNFN3TgD2isA2G5ygXEsGzWr9kpgN2PW5Ubxk3nAHyLRprFmS5M2FzdbTTZD2RjxfgH7xfbARZtdHc", + "slot": 386459128, + "block_time": 1765645486, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC" + ], + "funded": [ + "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ" + ] + }, + "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM": { + "address": "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12 + }, + "first_seen": 1784461923, + "birth_edge": { + "sink": "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 990500000, + "signature": "9duBpzRRMAZ2cQv2n79HKMzurHUBWmDWdDwNR8y6U48bnG6HKY2HZnrQxXNFVFpg8RtSy3KPNEY4ByMKHEBJCEb", + "slot": 433890247, + "block_time": 1784461923, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ": { + "address": "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1670 + }, + "first_seen": 1784580105, + "birth_edge": { + "sink": "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ", + "sources": [ + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm" + ], + "value": 49209767318, + "signature": "2BVoGzFZrFnuRLqpW1EAHxkFXmnp7xWp1wog4n3EBnCbTgQLvSgFVZHB8z4Z1FgdzjYGBh9tTUFZ1aLF2XtYCuKU", + "slot": 434168198, + "block_time": 1784580105, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm" + ], + "funded": [ + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" + ] + }, + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN": { + "address": "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18203 + }, + "first_seen": 1712861701, + "birth_edge": null, + "fee_payers": [ + "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J" + ], + "funded": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ] + }, + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7": { + "address": "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 48 + }, + "first_seen": 1784809669, + "birth_edge": { + "sink": "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 5973000000, + "signature": "3MSs2ea12TniywAz7iM9anGzcgJr8oB1kCVxVQm8YgQxqoCwwPpzBhg6EACKhpDuWoT6J723YZfrnDq8qqfLSTRJ", + "slot": 434715093, + "block_time": 1784809669, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1" + ] + }, + "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k": { + "address": "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 955 + }, + "first_seen": 1781405876, + "birth_edge": null, + "fee_payers": [ + "9HGyyJnWK4b67E97bB4SXpjbxpYmTBMaKNmWmfmYe4DK" + ], + "funded": [] + }, + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B": { + "address": "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B", + "owner": null, + "executable": false, + "signatures": { + "Exact": 15 + }, + "first_seen": 1737447329, + "birth_edge": { + "sink": "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B", + "sources": [ + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "value": 672750485, + "signature": "4gBVShePEtPdc8c9vLekxqxtKnHN1CaY2ECsfrU3Ysoo7AeQGVwwpqLDW9aAWYF3KSsekeoaA1aqHrTqAEHwSGuN", + "slot": 315387958, + "block_time": 1737447329, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "funded": [ + "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx" + ] + }, + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv": { + "address": "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1774644756, + "birth_edge": { + "sink": "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 5973000000, + "signature": "5fTttEt3Pb3F4Sj5dKKrkqFCZEJqR37qyLLYVSA7GwBqbyn4MCv2LDEZBKuh9pGRhdxTJa7xNMKH6c53gXrno7RZ", + "slot": 409284059, + "block_time": 1774644756, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY" + ] + }, + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF": { + "address": "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF", + "owner": null, + "executable": false, + "signatures": { + "Exact": 253 + }, + "first_seen": 1730807547, + "birth_edge": null, + "fee_payers": [ + "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP" + ], + "funded": [ + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" + ] + }, + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw": { + "address": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 14493 + }, + "first_seen": 1777091838, + "birth_edge": { + "sink": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 18571768434, + "signature": "44VV33bxUiT3rDmG3dtN2uxnDx4uBs6keq7PWCmx3rdqWhvPmPXJhrA7Sq7atABrqAkpaBZH5wdRRWMkFQ7j8kNA", + "slot": 415493545, + "block_time": 1777091838, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs": { + "address": "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7899 + }, + "first_seen": 1731918662, + "birth_edge": { + "sink": "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", + "sources": [ + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" + ], + "value": 11000000000, + "signature": "4NrJoknq1Jx9gtAQ8bxTuhWoNxMWggLDQS4XBN1zsNRVXGfCvC21JBkKGsxLH9hppZBa6KxaMLve5iiowk75DVLb", + "slot": 302103526, + "block_time": 1731918662, + "ambiguous_attribution": false + }, + "fee_payers": [ + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" + ], + "funded": [ + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + ] + }, + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G": { + "address": "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1781110081, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg" + ] + }, + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE": { + "address": "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784771569, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV" + ] + }, + "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT": { + "address": "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784758056, + "birth_edge": { + "sink": "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", + "sources": [ + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU" + ], + "value": 90000000000, + "signature": "2yFwMANAjHWBoCUWXwsF2NS8Vm24oSkE5oJ6RfsDe2jPVeo782fE98nePAJqpuxWL1VkA5iQXoXdDoU8SqopCTR2", + "slot": 434591833, + "block_time": 1784758056, + "ambiguous_attribution": false + }, + "fee_payers": [ + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU" + ], + "funded": [] + }, + "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq": { + "address": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 65 + }, + "first_seen": 1784659136, + "birth_edge": { + "sink": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 32880718309, + "signature": "4wZbNhexVa96s82hAQbdZ9Pq1fqencGxxmKYCwghVjT6iwxMLyyKBssfwvWVJFXFMXxNwSfrDo8TqZ4AiJbEPC6U", + "slot": 434356458, + "block_time": 1784659136, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC": { + "address": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 294 + }, + "first_seen": 1783054209, + "birth_edge": { + "sink": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", + "sources": [ + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx" + ], + "value": 180834295, + "signature": "55tLEDwJyuDGaSo8c6yYz4bj5utz7zJiVtQnsQuwqKg7abdYWVoK1vFkLQd7pyJhjh4r8ku6i9oCyXRa8a6kX18z", + "slot": 430452695, + "block_time": 1783054209, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx" + ], + "funded": [] + }, + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf": { + "address": "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894200, + "birth_edge": { + "sink": "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf", + "sources": [ + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ" + ], + "value": 849114919, + "signature": "5Gb3um1roTYvbjCFckDeFVPkU7QxKeShYSL3KWDxSEX8qhCrwgDEJgbSiuL3L4tCXzk1GaguKXBbmAU2yjAVmkdG", + "slot": 394707716, + "block_time": 1768894200, + "ambiguous_attribution": false + }, + "fee_payers": [ + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ" + ], + "funded": [ + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94" + ] + }, + "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk": { + "address": "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1152 + }, + "first_seen": 1777988933, + "birth_edge": { + "sink": "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "sources": [ + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" + ], + "value": 2362855000, + "signature": "Vdct3Y5hjgKy2gHHjimC9YE8faisFo7jz3EiFYjmb3yAYuHpY2tE5ZqJcXhMNtbrrXTRr5CxYpCz5djyYzwgJyb", + "slot": 417761237, + "block_time": 1777988933, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" + ], + "funded": [] + }, + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho": { + "address": "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho", + "owner": null, + "executable": false, + "signatures": { + "Exact": 600 + }, + "first_seen": 1784462427, + "birth_edge": { + "sink": "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho", + "sources": [ + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva" + ], + "value": 51022180729, + "signature": "4udxkWqxkyLqBAZpUJHNMkZDcFMVTq1BpaC2A5YqNGbm6He6WAYpQGQXMY5J91YcUxxZATqVkJooVP9y3EXqpABd", + "slot": 433891452, + "block_time": 1784462427, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva" + ], + "funded": [ + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP" + ] + }, + "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp": { + "address": "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF": { + "address": "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1744045119, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" + ] + }, + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva": { + "address": "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1629 + }, + "first_seen": 1784212802, + "birth_edge": { + "sink": "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva", + "sources": [ + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE" + ], + "value": 22180583705, + "signature": "59BrgexB78Kj9fCJkHmKs2UZYsp5Ntc5ia4RpE3T1WS8xtwyJEhn49w5VcofBvhkHWa9AxfEQsR1L6qjNFKdZnhj", + "slot": 433291830, + "block_time": 1784212802, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE" + ], + "funded": [ + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho" + ] + }, + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS": { + "address": "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784769521, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m", + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn" + ] + }, + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ": { + "address": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19785 + }, + "first_seen": 1780029667, + "birth_edge": { + "sink": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 2442413156, + "signature": "2UXxvAd3GMF7t1ZMWhrXJcFP3q6iYYcZBEMeynG6PWWz8vrttyjdwXo1KSxrYhGU8zchaaM6979gnU1mz4d3dHKr", + "slot": 422867536, + "block_time": 1780029667, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [] + }, + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe": { + "address": "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 737 + }, + "first_seen": 1784227892, + "birth_edge": { + "sink": "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe", + "sources": [ + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor" + ], + "value": 130000000, + "signature": "kTyjASGv9R4C1zjLJriyX96k2ZWgbAwPw3Qd2z3pCzb5cRPuZs3vt8uqzKH2bjr5AdqMDwf8SnxCqbwyt2cWwdm", + "slot": 433328336, + "block_time": 1784227892, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor" + ], + "funded": [ + "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt" + ] + }, + "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq": { + "address": "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 14 + }, + "first_seen": 1784552253, + "birth_edge": { + "sink": "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "sources": [ + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW" + ], + "value": 23698818508, + "signature": "3uywYCF5VuSzVQkMX6Pghx7SXTsJuW55Jv37zcHt1foh71Nuq6vGrb4keCz7JNFjLMRWkK5Aq2jNmWhqkWbXuH8y", + "slot": 434103135, + "block_time": 1784552253, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW" + ], + "funded": [] + }, + "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC": { + "address": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4402 + }, + "first_seen": 1776709519, + "birth_edge": { + "sink": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 4909585890, + "signature": "5VEpmW6pcMbLtJQHaJbctF8BUkixM7cH6gwV5yMHLtJtq5DaCpFaKRydWVjQ85d3uA9ErzyjYTkqyfHriUeN1D8E", + "slot": 414528392, + "block_time": 1776709519, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [] + }, + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165": { + "address": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1776477179, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9": { + "address": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 41 + }, + "first_seen": 1784377766, + "birth_edge": { + "sink": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", + "sources": [ + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN" + ], + "value": 1299788806, + "signature": "DonAnKr8whXoDnWemm9bqSjoUQdEohd9sus4nhstSat4cF5HxAmnrHQqMEbbNW3s9KhRDYLvL28ifTpBj16tWha", + "slot": 433689104, + "block_time": 1784377766, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN" + ], + "funded": [] + }, + "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k": { + "address": "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 112 + }, + "first_seen": 1783337142, + "birth_edge": { + "sink": "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "sources": [ + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w" + ], + "value": 2000000000, + "signature": "3TYxeVhY5FHcpE3BqdMtf6RUScLkjcsXBpZnPjc8YfGiTXw25vLWWvd3vZWPE9V6kBcLpzMrph82uRoprzHPNvKU", + "slot": 431153926, + "block_time": 1783337142, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w" + ], + "funded": [] + }, + "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v": { + "address": "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 8839 + }, + "first_seen": 1769181912, + "birth_edge": null, + "fee_payers": [ + "BidtDwH9WbEvm9T6tqqsEgAscTzLJeX6H2rGbw22jR39" + ], + "funded": [] + }, + "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ": { + "address": "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 45 + }, + "first_seen": 1783637441, + "birth_edge": { + "sink": "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 22952461058, + "signature": "63UUpBFp2VbRkpjKsc3FaXXucqtzjTyPZCsqEtoXBxNiW263FxXHdr6Coo7FksLHC3amGnSZoV9rD9uR4G4yKMfo", + "slot": 431891370, + "block_time": 1783637441, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE": { + "address": "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3053 + }, + "first_seen": 1761711549, + "birth_edge": { + "sink": "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "sources": [ + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" + ], + "value": 10000000, + "signature": "42BSukNNZwdKRuESCNwP7Dr8xn7HmRc21PNP6PdSa36WrF1mbw2n3CatJwkBzviQiutmRPDn9oCR7bJo4jXDVtzM", + "slot": 376499867, + "block_time": 1761711549, + "ambiguous_attribution": false + }, + "fee_payers": [ + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" + ], + "funded": [] + }, + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ": { + "address": "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894187, + "birth_edge": { + "sink": "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ", + "sources": [ + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x" + ], + "value": 849119919, + "signature": "5hopWZ3eVdGLYmfhGBcqbvTV2SGzmQc8X3KyyfBQ36QzCcyGtMkxBKhTLuYwftq866p9ZdaZFVMbrsRNNGjzE3Cm", + "slot": 394707684, + "block_time": 1768894187, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x" + ], + "funded": [ + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf" + ] + }, + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu": { + "address": "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784715142, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn" + ] + }, + "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW": { + "address": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 108 + }, + "first_seen": 1784490766, + "birth_edge": { + "sink": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "sources": [ + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM" + ], + "value": 150000000, + "signature": "2uKVQ2Do2Vandv4rqHhJQZEFKrKQGFQdt15BtdaknzdY8Rxas2Bx3smqkhK6D3Hz8urRCANByPKEJxDcCAKTaq3G", + "slot": 433959006, + "block_time": 1784490766, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM" + ], + "funded": [] + }, + "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD": { + "address": "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1785020655, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT" + ] + }, + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU": { + "address": "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 583 + }, + "first_seen": 1769032382, + "birth_edge": { + "sink": "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU", + "sources": [ + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi" + ], + "value": 20000000, + "signature": "AG1dX11PZCL7yBeTVs4PuNNECWsZ7tuvE94MP16zRMdtgyqchb5gyD512vVb7bd5pk3rYHpfg1RFFCwSLaahc4w", + "slot": 395055111, + "block_time": 1769032382, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi" + ], + "funded": [ + "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT" + ] + }, + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q": { + "address": "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q", + "owner": null, + "executable": false, + "signatures": { + "Exact": 9987 + }, + "first_seen": 1753767232, + "birth_edge": null, + "fee_payers": [ + "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r" + ], + "funded": [ + "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE" + ] + }, + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf": { + "address": "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2651 + }, + "first_seen": 1737914051, + "birth_edge": { + "sink": "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", + "sources": [ + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" + ], + "value": 50000000000, + "signature": "2rpBPJocSrWKt2G59e29e8Tj7MJDUAQLEKx2GaoAApyUBLbynToft6iDKYZKTWr2BL9UhfiwV7qr6E1exQn6mnAP", + "slot": 316536578, + "block_time": 1737914051, + "ambiguous_attribution": false + }, + "fee_payers": [ + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" + ], + "funded": [ + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" + ] + }, + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe": { + "address": "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1994 + }, + "first_seen": 1762263640, + "birth_edge": { + "sink": "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "sources": [ + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + ], + "value": 10000000, + "signature": "2abx2yUwotMytWNz2EAiW6RTrAsz9otzWMXKPyVN8FjtaEa3hp4MTJYi2C3R8XrnUND8MyuMJ5T2yKEb4prkrBVJ", + "slot": 377877154, + "block_time": 1762263640, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + ], + "funded": [ + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz" + ] + }, + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6": { + "address": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 531 + }, + "first_seen": 1783364451, + "birth_edge": { + "sink": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "sources": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + ], + "value": 1100000000, + "signature": "4kLBV2LbExCtK85mRP4FnNGDaHVTBNNvvtyQjbFVqiqeLgvs3fcSUVbsxtmuZ6WzVUBYV9h9GJzyUuWpYf6nAxTD", + "slot": 431221166, + "block_time": 1783364451, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + ], + "funded": [] + }, + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx": { + "address": "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 74 + }, + "first_seen": 1776857831, + "birth_edge": { + "sink": "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx", + "sources": [ + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" + ], + "value": 1000000000, + "signature": "4mEmeFnTKjqyT3XM3UZbp7fGA5gWeMB8ny87kZJbBbGGW89NJv5i5GBSvAJqx4JQN4eKDKNUomka7gE4AvgLRm8v", + "slot": 414902368, + "block_time": 1776857831, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" + ], + "funded": [ + "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do" + ] + } + } +} \ No newline at end of file diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index 22be16d0..01039155 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -183,6 +183,60 @@ is what this document reports — including a refusal, and including a ρ that s worse for the argument than Run 3's. If it fails on the RPC gate, that is recorded as a failed run and not retried into success. +### Result + +Helius, archival probes passed. 1,613 calls at 3.5 requests per second, 456 +seconds wall clock. + +``` +attempted 84 | resolved 50 | evidence-unresolved 6 | budget-unresolved 28 + | scope-unresolved 0 | rpc failures 0 (0.00%) +``` + +| quantity | Run 3 | Run 4 | +|---|---|---| +| resolved members | 40 | **50** | +| budget-unresolved | 39 | **28** | +| rpc failures | 0 | 0 | +| provenance classes observed | 17 | 22 | +| **loss factor ρ (point)** | 0.1219 | **0.1032** | +| **ρ (unresolved bracket)** | 0.0253 … 0.1837 | **0.0316 … 0.1318** | +| bracket width (ratio) | 7.3× | **4.2×** | +| effective-k, Shannon | 4.88 | 5.16 | +| Good–Turing coverage | 0.65 | **0.62** | +| Chao1 richness | 108 | **193** | + +**The informativeness gate clears.** 50 of 84 resolved, against a threshold of +42. The bracket narrows from a 7.3-fold span to a 4.2-fold one, and both +readings now sit on the same side of 0.15. This is the first run in this +document that produces a headline. + +**The under-sampling flag still fires, and it got worse.** This is the part +worth reading twice. Resolving ten more members did not improve coverage — it +*degraded* it, from 0.65 to 0.62, and pushed the Chao1 richness estimate from +108 classes up to 193. + +That is not a defect and not noise. The additional members did not land in the +classes already observed; they landed in new ones, mostly alone. Under +Good–Turing, coverage falls when the share of singletons rises, so a sample that +keeps discovering fresh singleton classes reports *less* confidence as it grows. + +The consequence matters for the conclusion. Under-sampling here is **a property +of the provenance distribution, not an artifact of our budget.** Run 3 left it +open whether more resolution would close the tail; Run 4 answers that, and the +answer is no. Spending more calls would raise the resolved count and lower the +coverage further. There is no budget at which this frame's class distribution +becomes well-observed, because the tail is where the mass is. + +So the honest statement of what was measured is: **ρ ≈ 0.10 on this pool, inside +0.032 … 0.132, from a sample whose class distribution is demonstrably +heavy-tailed and two-thirds unobserved — and the second fact is now a finding +rather than a caveat.** + +**Stopping rule honoured.** No further run against this frame. The Run 3 +artifact stays committed beside the Run 4 one; both are in `data/`, and the +numbers above are recomputable from either without RPC access. + ## What we do not conclude Nothing about how private Privacy Cash is. ρ = 0.1219 is a point estimate inside From 665d22cc073950f220b62204f517e46903a7a81a Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sat, 25 Jul 2026 23:59:30 -0300 Subject: [PATCH 33/83] docs: argue from the failure mode, not from anyone else's repository Every comparison in this repository pointed outward -- "a competing submission does X", 46 of them across the README, four design documents, the Makefile and twenty source comments. That framing was wrong on two counts. It reads as insecurity rather than confidence, and it dates badly: a claim about someone else's code is false the moment they fix it. The technical content is retained and, in most places, strengthened. Each comparison becomes a statement about the failure mode itself -- what the trap is, why a competent implementer walks into it, and what this codebase does instead. "A competing tracer scans the six most recent transactions" becomes an explanation of why newest-first paging makes that the path of least resistance and why it fails silently. Nothing is softened. The drains, the delta-only ceremony check, the tautological metric and the unwrap_or(0) census are all still here, in more detail than before, and now stand on their own reasoning. --- Makefile | 7 +- README.md | 117 +++++++++++---- crates/mirror-circuit/src/prover.rs | 14 +- crates/mirror-circuit/src/solana.rs | 8 +- crates/mirror-circuit/tests/onchain_layout.rs | 4 +- crates/mirror-cli/src/main.rs | 11 +- crates/mirror-core/src/hash.rs | 12 +- crates/mirror-core/src/merkle.rs | 15 +- crates/mirror-core/src/note.rs | 9 +- crates/mirror-provenance/src/classify.rs | 15 +- crates/mirror-provenance/src/frame.rs | 6 +- crates/mirror-provenance/src/outcome.rs | 7 +- crates/mirror-provenance/src/trace.rs | 15 +- docs/ARCHITECTURE.md | 30 ++-- docs/PLAN.md | 68 ++++----- docs/PROVENANCE_METHOD.md | 134 +++++++++++------- docs/THREAT_MODEL.md | 9 +- programs/mirror-pool/src/error.rs | 4 +- programs/mirror-pool/src/processor.rs | 17 +-- programs/mirror-pool/src/state.rs | 20 +-- 20 files changed, 327 insertions(+), 195 deletions(-) diff --git a/Makefile b/Makefile index c116b542..129390a0 100644 --- a/Makefile +++ b/Makefile @@ -8,9 +8,10 @@ # target directory removes the collision. # # The program deliberately stays a member of the root workspace so that -# `cargo test --workspace` covers it. Excluding it would hide its tests from CI, -# which is how a competing submission ended up never running a single one of its -# on-chain tests. +# `cargo test --workspace` covers it. Excluding it from the workspace is the +# usual way to dodge the target-directory collision above, and it silently +# removes every on-chain test from CI: the suite still passes, because it no +# longer runs. # # The path must be absolute. cargo resolves a relative CARGO_TARGET_DIR against # the manifest directory, so `target/sbf` with `--manifest-path programs/...` diff --git a/README.md b/README.md index b1fc92e4..6515f57d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,18 @@ # mirror-pool -A behavioral anonymity set for Solana. Members deposit a fixed denomination; -later, a member proves in zero knowledge that they own some note in the set and -directs the pool to act. The pool executes. An observer sees that an action -happened and cannot say which member asked for it. +A behavioural anonymity set for Solana — privacy for **what you do**, not for +what you hold. + +Members join a pool by depositing a fixed denomination. Later, a member proves in +zero knowledge that they own some note in the set and directs the pool to perform +an *action*: an arbitrary instruction on an arbitrary program. The pool performs +it, signed by the pool, batched with everyone else's at one timestamp. An +observer sees that a stake, a swap or a vote happened and cannot say which member +asked for it. + +Moving lamports is the degenerate case, selector zero. The interesting case is +selector one, and the end-to-end suite runs it against a real deployed program: +four members, four memos, one slot, one signer — [see below](#synchronised-actions-are-the-point). Rust end to end. MIT. No Anchor, no Circom, no JavaScript anywhere in the proving path. @@ -14,15 +23,16 @@ make verify # fmt, clippy -D warnings, tests, build-sbf ## The problem this takes as its subject -Every serious submission to this bounty — across all three repositories — -identifies the same open channel and none of them closes it: +There is one channel that no deposit-based anonymity set on a public ledger +closes, and it is not something a better circuit can fix: > An anonymity set on a public ledger can be partitioned by **where each member's > capital came from**. Learning a member's funding class leaves only that class to > guess within, so what survives is the size of the class, not `k`. -No deposit pool controls where its users' money came from, so this cannot be -fixed by a better circuit. What it can be is *measured*, and measured honestly. +No pool controls where its users' money came from. What that channel can be is +*measured*, and measured honestly — which as far as we can tell nobody has done +against live Solana data with a published method. So this submission claims exactly two things: @@ -62,6 +72,48 @@ produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. +## Synchronised actions are the point + +The brief asks for an anonymity set for *behaviour*, not for funds. That +distinction is load-bearing here, so it is tested rather than asserted: + +``` +settled 4 real CPI actions in one transaction, 39,636 CU +``` + +`a_crowd_of_members_perform_a_real_protocol_action_together` seeds a pool, +loads the **real SPL Memo program** into the SVM, and has four members each +prove membership and request the identical memo. Settlement invokes Memo four +times in a single transaction, every invocation signed by the pool's vault PDA. + +What an observer holds afterwards is four identical memos, one timestamp, one +signer, and no field anywhere in the transaction that distinguishes which member +asked for which. The actions are indistinguishable **by content** because the +payloads match, and indistinguishable **by timing** because settlement gives them +one clock. + +Two properties make this a behavioural tool rather than a mixer with a CPI bolted +on: + +- **The target is arbitrary.** Selector one invokes any program with any payload. + Staking, voting and swapping are the same code path as the memo; Memo is used + in the test because it is small, real, and validates its own account list. +- **Moving lamports is the degenerate case.** Selector zero is a plain transfer, + kept only because expressing "pay this account" should not require a target + program. + +A separate test passes a **non-empty account list** through the CPI, which +matters because SPL Memo requires every account handed to it to have signed. If +the program dropped an account, mislabelled a signer flag, or miscounted, Memo +rejects — so the account plumbing is load-bearing in that test rather than +decorative. + +The honest limit: the action binding commits to *how many* accounts an action +takes, not *which*. Settlement is permissionless, so a settler chooses them. For +a target whose destination lives in an account rather than in instruction data, +that is a real gap, and `docs/THREAT_MODEL.md` states it rather than working +around it. + ## Properties, and how each is checked **A note's value cannot disagree with its commitment.** The denomination is a @@ -73,11 +125,16 @@ from the arithmetic that moved them. **A nullifier is spent once, ever** — never epoch-scoped. Those two together make a class of drain *unrepresentable* rather than merely -untested. Two competing submissions are drainable at exactly this point: one -escrows an amount never bound to its hidden commitment, so a depositor of one -lamport can withdraw the whole pool with a valid proof; the other issues an -epoch-scoped nullifier against a value payout, so one deposit pays out once per -epoch forever. +untested, and the class is worth naming because a shielded pool built the +obvious way lands in it. Carry the amount as a field in the note and forget to +bind it to the commitment, and a depositor of one lamport withdraws the whole +pool holding a perfectly valid proof. Scope the nullifier to an epoch — natural +if epochs are how you batch — and one deposit pays out once per epoch, forever. + +Neither is reachable here. The denomination is not in the note, so there is no +amount to bind; and the nullifier set is global, so an epoch boundary cannot +reopen a spend. Both are pinned by tests that assert the rejection rather than +assuming it. **A relay cannot redirect or re-price an action.** The action binding is never transmitted — it is recomputed on-chain from the selector, the target program, @@ -104,8 +161,9 @@ pinned to circomlib's published `poseidon([1,2])` vector rather than to each other, so the two agreeing on a wrong answer would need circomlib's own vector to be wrong. The syscall is then checked against the host on-chain: the end-to-end suite asserts the root the deployed program builds equals the root the host -built. Several published Solana projects ship a gadget whose native and -in-circuit hashes differ; that only surfaces at proving time. +built. A gadget whose native and in-circuit hashes disagree is the classic +expensive failure here, because nothing catches it until proving time and the +symptom — proofs that verify nowhere — points at everything except the hash. ## The measurement @@ -127,16 +185,22 @@ run, and Run 4's budget and predictions were committed to git *before* the collection started, so the parameters are a declaration rather than a description. -### Design choices that exist to avoid specific published defects +### Design choices, and the failure each one exists to avoid + +Every item here is a decision that a reasonable implementation gets wrong by +default. They are listed because a provenance number is only as good as the +weakest of them, and none of them is visible in the output. - **Edges come from balance deltas**, not instruction parsing, which is blind to every program that moves lamports by direct account mutation. -- **The birth edge is the oldest credit.** A competing tracer scans the six most - *recent* transactions — the wrong end of the history for anything with more - than six, which manufactures "unresolved" for active wallets. -- **The hub threshold is decoupled from the paging cap.** In a competing tracer - the two are one number, so "reaches an attributable origin" there means "hit - the RPC page cap" — admitting every DEX program and bot. +- **The birth edge is the oldest credit**, so the walk goes to the *start* of a + wallet's history. Reading the most recent transactions instead is the wrong end + of the record for any wallet with more than a handful, and manufactures + "unresolved" for precisely the active wallets worth resolving. +- **The hub threshold is decoupled from the paging cap.** Collapse them into one + number — easy to do, since both are "how many signatures do we look at" — and + "reaches an attributable origin" quietly becomes a synonym for "hit the RPC page + cap", which admits every DEX program and trading bot as an origin. - **RPC failures are never evidence.** Counted separately, excluded from the distribution, and above a 1% failure rate the run refuses to print a headline rather than printing a warning above one. @@ -193,10 +257,11 @@ multi-party ceremony, not more SOL. public, so proofs are forgeable. `mirror verify-setup` re-derives the key from the public seed and compares it element by element against the one compiled into the program — expected digest - `b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7`. A competing submission - publishes its entropy string *and* gitignores its proving key, so its setup is - insecure and unreproducible at once — no third party can produce a valid proof - for its deployed program at all. + `b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7`. Reproducible + and insecure is a coherent position for an unaudited submission; the incoherent + one is a setup that is *both* insecure and unreproducible, which is what + publishing the entropy while withholding the proving key produces — nobody can + verify the key, and nobody can regenerate it either. - The on-chain `k_floor` bounds **program-visible membership** only. That is all a program can check. - Not audited. diff --git a/crates/mirror-circuit/src/prover.rs b/crates/mirror-circuit/src/prover.rs index e3a42b2e..aa0cd150 100644 --- a/crates/mirror-circuit/src/prover.rs +++ b/crates/mirror-circuit/src/prover.rs @@ -54,12 +54,14 @@ pub fn generate(rng: &mut R) -> Result Result { let mut expanded = [0u8; 32]; for (i, slot) in expanded.iter_mut().enumerate() { diff --git a/crates/mirror-circuit/src/solana.rs b/crates/mirror-circuit/src/solana.rs index 8479c112..46b3a126 100644 --- a/crates/mirror-circuit/src/solana.rs +++ b/crates/mirror-circuit/src/solana.rs @@ -88,9 +88,11 @@ impl SolanaVerifyingKey { /// A stable digest over the whole key, in a fixed order. /// - /// This is what a setup transcript must bind. A competing submission's - /// ceremony verifier checks only `delta`, so it will happily certify a - /// transcript whose verifying key belongs to an entirely different circuit. + /// This is what a setup transcript must bind, and it must be the *whole* + /// key. Checking only `delta` is the tempting shortcut — it is the element a + /// contribution actually changes — but it leaves `alpha`, `beta`, `gamma` + /// and every `IC` point unconstrained, so such a verifier would happily + /// certify a transcript whose key belongs to an entirely different circuit. pub fn digest_preimage(&self) -> Vec { let mut buf = Vec::with_capacity(64 + 128 * 3 + self.ic.len() * 64 + 8); buf.extend_from_slice(&self.alpha_g1); diff --git a/crates/mirror-circuit/tests/onchain_layout.rs b/crates/mirror-circuit/tests/onchain_layout.rs index f9dcd67d..edc20935 100644 --- a/crates/mirror-circuit/tests/onchain_layout.rs +++ b/crates/mirror-circuit/tests/onchain_layout.rs @@ -127,8 +127,8 @@ fn a_non_negated_proof_a_is_rejected() { #[test] fn the_setup_is_reproducible_from_its_seed() { // Anyone can re-derive the deployed verifying key from the committed seed. - // A competing submission gitignores its proving key and publishes its - // entropy string, so its setup is both insecure and unreproducible. + // Publishing the entropy while withholding the key is the failure this + // guards against: insecure and unreproducible at the same time. let a = generate_reproducible(SEED).unwrap().solana_vk(); let b = generate_reproducible(SEED).unwrap().solana_vk(); assert_eq!(a, b); diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index c6c14c74..8cd78633 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -16,9 +16,10 @@ mod soak; /// waste is public, so proofs are forgeable. That trade is stated rather than /// hidden, and the production path is a multi-party ceremony. /// -/// A competing submission publishes its entropy string *and* gitignores the -/// proving key, so its setup is insecure and unreproducible at the same time — -/// no third party can produce a valid proof for its deployed program at all. +/// Reproducible-and-insecure is a coherent position for an unaudited tool. The +/// incoherent one is publishing the entropy *and* withholding the proving key, +/// which is insecure and unreproducible at once: the toxic waste is public, and +/// no third party can regenerate the key to produce a valid proof at all. pub const DEV_SETUP_SEED: &str = "mirror-pool-reproducible-dev-setup-v1"; #[derive(Parser)] @@ -141,8 +142,8 @@ enum Command { /// /// This is the check a third party runs. It binds the *whole* key — alpha, /// beta, gamma, delta and every IC point — not just delta, so a transcript - /// carrying a key belonging to a different circuit cannot pass. A competing - /// submission's ceremony verifier checks delta alone and would certify one. + /// carrying a key belonging to a different circuit cannot pass. Verifying + /// delta alone is the common shortcut and it would certify exactly that. VerifySetup { #[arg(long, default_value = DEV_SETUP_SEED)] seed: String, diff --git a/crates/mirror-core/src/hash.rs b/crates/mirror-core/src/hash.rs index 34639cb5..b424a622 100644 --- a/crates/mirror-core/src/hash.rs +++ b/crates/mirror-core/src/hash.rs @@ -30,8 +30,9 @@ //! not practically exploitable, but arity separation removes the question //! entirely at no cost. //! -//! A competing implementation in this bounty hashes its Merkle nodes, its -//! nullifiers and its action bindings with one untagged arity-2 function. +//! Hashing Merkle nodes, nullifiers and action bindings with one untagged +//! arity-2 function is the shortcut this avoids: it works, right up until a +//! value from one domain is accepted where another was meant. use crate::{Field, MirrorError}; use solana_poseidon::{hashv, Endianness, Parameters}; @@ -75,9 +76,10 @@ pub fn commitment(k: Field, r: Field, denom_tag: Field) -> Result Result { poseidon1(k) } diff --git a/crates/mirror-core/src/merkle.rs b/crates/mirror-core/src/merkle.rs index 380cf97e..bb2ce206 100644 --- a/crates/mirror-core/src/merkle.rs +++ b/crates/mirror-core/src/merkle.rs @@ -8,10 +8,10 @@ //! paths that go into a proof. //! //! They must agree, and the tests here assert that they do over real insertion -//! sequences. That assertion is the point: a competing submission in this bounty -//! has no test connecting its on-chain accumulator to a proof at all, and -//! injects a fixture root instead, with a comment conceding that a real deposit -//! sequence cannot reproduce it. +//! sequences. That assertion is the point of this module. It is easy to test +//! each half separately and never connect them — proving against an injected +//! fixture root rather than one the on-chain accumulator actually produced — and +//! a suite built that way stays green while the two implementations drift. use crate::{hash_node, Field, MirrorError}; @@ -371,9 +371,10 @@ mod tests { } } - /// The assertion a competing submission is missing entirely: the on-chain - /// accumulator and a host-generated proof must agree after a real sequence - /// of deposits, not after a fixture is injected. + /// The assertion that connects the two halves: the on-chain accumulator and + /// a host-generated proof must agree after a real sequence of deposits, not + /// after a fixture root is injected. Without this test the two can drift + /// apart indefinitely with every other test still passing. #[test] fn the_frontier_root_accepts_a_host_generated_proof() { let mut frontier = Frontier::new().unwrap(); diff --git a/crates/mirror-core/src/note.rs b/crates/mirror-core/src/note.rs index 3a1edf8d..4e0322a8 100644 --- a/crates/mirror-core/src/note.rs +++ b/crates/mirror-core/src/note.rs @@ -6,10 +6,11 @@ //! which the program records permanently. //! //! The denomination is a pool constant rather than a field inside the note, so -//! the escrowed lamports and the hidden commitment cannot disagree. A competing -//! submission escrows an amount that is never bound to its commitment, which -//! lets a depositor of one lamport withdraw the whole pool with an entirely -//! valid proof. Here that state is not representable. +//! the escrowed lamports and the hidden commitment cannot disagree. Carrying the +//! amount inside the note is the obvious alternative and it is a trap: unless +//! the amount is bound into the commitment *and* checked against the escrow, a +//! depositor of one lamport withdraws the whole pool holding an entirely valid +//! proof. Here that state is not representable rather than merely rejected. use crate::{commitment, nullifier, Field, MirrorError}; diff --git a/crates/mirror-provenance/src/classify.rs b/crates/mirror-provenance/src/classify.rs index 9e8bf8e8..5095dfd3 100644 --- a/crates/mirror-provenance/src/classify.rs +++ b/crates/mirror-provenance/src/classify.rs @@ -23,10 +23,12 @@ use std::collections::{BTreeMap, BTreeSet}; pub struct Thresholds { /// Lifetime signatures at or above which an address is a volume hub. /// - /// Deliberately far below the paging cap. The competing tracer sets its hub - /// threshold *equal* to the RPC page limit, so "is a hub" there means "hit - /// the page cap" — which admits every DEX program and bot while excluding a - /// genuine exchange withdrawal address with 800 transactions. + /// Deliberately far below the paging cap, and the two must never be the + /// same constant. They sound like the same question — "how many signatures + /// do we care about" — but setting the hub threshold equal to the RPC page + /// limit makes "is a hub" mean "hit the page cap", which admits every DEX + /// program and bot while excluding a genuine exchange withdrawal address + /// with 800 transactions. pub hub_signatures: u64, /// Minimum account age for the hub rule, in seconds. A young address with /// many transactions is a bot or an airdrop, not an origin. @@ -386,8 +388,9 @@ mod tests { assert_eq!(out.label().unwrap(), "busy-unlabelled:Busy1"); } - /// The distinction the competing measurement collapses: a busy address is - /// not an attributable origin, and its label must never read like one. + /// The distinction that is easiest to collapse and most costly to lose: a + /// busy address is not an attributable origin, and its label must never + /// read like one. #[test] fn a_volume_hub_is_labelled_as_unnamed() { let env = Env::new(); diff --git a/crates/mirror-provenance/src/frame.rs b/crates/mirror-provenance/src/frame.rs index b2742aaa..7ae70eb1 100644 --- a/crates/mirror-provenance/src/frame.rs +++ b/crates/mirror-provenance/src/frame.rs @@ -18,9 +18,9 @@ //! population. //! //! **Nothing is excluded for being hard to trace.** Busy depositors stay in. -//! Dropping them would inflate the resolved fraction by construction, which is -//! the bias that makes a competing measurement's untraceable bucket look like a -//! finding about the pool. +//! Dropping them inflates the resolved fraction by construction, and whatever +//! the remaining sample then reports is a property of the exclusion rule rather +//! than a finding about the pool. use crate::edge::TransactionView; diff --git a/crates/mirror-provenance/src/outcome.rs b/crates/mirror-provenance/src/outcome.rs index bb6310d1..785ee45f 100644 --- a/crates/mirror-provenance/src/outcome.rs +++ b/crates/mirror-provenance/src/outcome.rs @@ -264,9 +264,10 @@ mod tests { #[test] fn a_volume_hub_is_not_an_attributable_origin() { - // Encoded as a test because the distinction is the whole critique of a - // competing measurement: there, "reaches an attributable origin" means - // "the address hit the RPC page cap". + // Encoded as a test because this is where a provenance tracer most + // easily launders a budget limit into a finding: if a volume hub counts + // as an origin, "reaches an attributable origin" degenerates into "the + // address hit the RPC page cap". let hub = Outcome::Terminal { rule: TerminalRule::VolumeHub, label: "busy-unlabelled:SomeAddress".to_string(), diff --git a/crates/mirror-provenance/src/trace.rs b/crates/mirror-provenance/src/trace.rs index e662d48b..fc816e4f 100644 --- a/crates/mirror-provenance/src/trace.rs +++ b/crates/mirror-provenance/src/trace.rs @@ -12,10 +12,11 @@ //! an address with fewer than one page of lifetime signatures the birth //! transaction is the **last element of the first page**. //! -//! A competing tracer scans the six most **recent** transactions of each -//! address. For anything with more than six transactions that is the wrong end -//! of the history, and it manufactures "unresolved" for exactly the active -//! wallets a funding trace most wants to follow. +//! Taking the first few entries of that page instead — the most **recent** +//! transactions — is the wrong end of the history for any address with more +//! than a page-fragment of activity. It also fails silently, reporting +//! "unresolved" where the truth is "we looked in the wrong place", and it does +//! so for exactly the active wallets a funding trace most wants to follow. //! //! ## Where pass one stops //! @@ -191,9 +192,9 @@ pub struct Manifest { /// one. A token account is not a person and cannot be a member of an /// anonymity set, so including it would measure something other than the /// pool. Excluding addresses because they looked *hard to trace* would be a - /// different thing entirely and is exactly the bias that inflates a - /// competing measurement's untraceable bucket — so the count is published - /// rather than quietly applied. + /// different thing entirely: that criterion correlates with the outcome, so + /// it manufactures whichever headline the exclusion implies. The count is + /// published rather than quietly applied, so the distinction is checkable. pub excluded_non_wallet: Vec, /// Share of observed credits whose source was one of several debited /// accounts, so the funder is a set rather than a single address. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e59f8f5d..02d3e5af 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -32,11 +32,12 @@ nullifier = H1(k) revealed on spend, spent once ever **The denomination is a pool constant, not a field in the note.** One pool serves one denomination, so the escrowed lamports and the hidden commitment cannot disagree. This is not a stylistic choice — it makes a class of drain -unrepresentable rather than merely untested. Two competing submissions in this -bounty are drainable at exactly this point: one escrows an amount that is never -bound to its commitment, so a depositor of one lamport can withdraw the whole -pool with an entirely valid proof; the other issues an epoch-scoped nullifier -against a value payout, so one deposit pays out once per epoch forever. +unrepresentable rather than merely untested, and the class is worth stating +because a shielded pool built the obvious way falls into it. Carry the amount as +a note field and fail to bind it to the commitment, and a depositor of one +lamport withdraws the entire pool holding a valid proof. Scope the nullifier to +an epoch — the natural move once epochs are how batches form — and a single +deposit pays out once per epoch, forever. Nullifiers here are spend-once, never epoch-scoped. @@ -205,12 +206,13 @@ Design choices that exist to avoid specific published defects: - Edges come from **balance deltas**, not instruction parsing, which is blind to every program that moves lamports by direct account mutation. -- The **birth edge** is the oldest credit. A competing tracer scans the six most - *recent* transactions, which is the wrong end of the history for anything with - more than six. -- The **hub threshold is decoupled from the paging cap**. In a competing tracer - the two are the same number, so "reaches an attributable origin" there means - "hit the RPC page cap". +- The **birth edge** is the oldest credit, so the walk reaches the *start* of a + wallet's history. Scanning the most recent transactions instead is the wrong + end of the record for any wallet with more than a handful of them. +- The **hub threshold is decoupled from the paging cap**. Making them the same + number is an easy collapse, since both answer "how many signatures do we look + at" — and it turns "reaches an attributable origin" into a synonym for "hit + the RPC page cap". - **RPC failures are never evidence.** They are counted separately and excluded from the distribution, and above a 1% failure rate the run refuses to print a headline rather than printing a warning above one. @@ -218,8 +220,10 @@ Design choices that exist to avoid specific published defects: rather than an error for pruned history, so a collection against one would look healthy and report every old funding event as absent. - Seeds that are not wallets are excluded on a **definitional** criterion and the - count is published. Excluding addresses for looking hard to trace would be a - different thing and is the bias that inflates a competing measurement. + count is published. Excluding addresses for *looking hard to trace* is a + different thing entirely — it drops exactly the members that would have + widened the class distribution, and inflates the result in the flattering + direction. The headline is the loss factor `ρ = 2^−H(C)` rather than effective-k, because it is independent of `k` and therefore comparable across pools. Effective-k measured diff --git a/docs/PLAN.md b/docs/PLAN.md index d00576fe..a2c099ad 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -68,12 +68,13 @@ commitment and can never disagree with the escrowed amount. **Accounting invariant**, asserted in tests: `vault.lamports >= D × (deposits − spends) + rent_exempt_minimum`. -This is deliberate. Two competing submissions are drainable precisely here: one -escrows an amount that is never bound to the hidden commitment, so a depositor of -one lamport can withdraw the pool with a valid proof; the other issues an -epoch-scoped nullifier against a value payout, so a single deposit pays out once -per epoch forever. A fixed denomination with spend-once nullifiers makes both -failures unrepresentable rather than merely untested. +This is deliberate, and it targets two drains that a shielded pool reaches by +building in the obvious direction. Carry the amount as a note field without +binding it to the hidden commitment, and a depositor of one lamport withdraws +the pool holding a valid proof. Scope the nullifier to an epoch — natural once +epochs are how batches form — and a single deposit pays out once per epoch, +forever. A fixed denomination with spend-once nullifiers makes both +unrepresentable rather than merely untested. ### Circuit — 3 public inputs @@ -87,8 +88,9 @@ with root `R`, my nullifier is `Poseidon(k)`, and this proof is bound to | `nullifier` | the program records it to prevent replay | | `action_binding` | the program recomputes it, so a relay cannot redirect or re-price | -Measured cost is `74,179 + 5,661 × N` CU, so three inputs land near 91k CU — -comparable to the strongest competing implementation while binding strictly more. +Measured cost is `74,179 + 5,661 × N` CU, so three inputs land near 91k CU. That +is the going rate for on-chain Groth16 on Solana, and the third input buys the +relay binding rather than being spent on a value that could have been folded in. Any further value we need to commit to gets folded into `action_binding` rather than added as a fourth input. See `GROTH16_INTEGRATION.md` for the verified conversion path and its pitfalls. @@ -111,21 +113,24 @@ So the protocol splits it: - **Phase 2, at epoch close.** The pool executes every ticket's action in one settlement, sharing a single timestamp and ordering. -This also fixes a structural leak in a competing design, where every participant -must sign the settlement transaction — which publishes the entire membership set -by pubkey in a single transaction. Here no member key ever appears on chain. +The two phases also close a structural leak that a single-phase design walks +into: if settlement is one transaction that every participant must sign, then +that transaction publishes the entire membership set by pubkey, and the +anonymity set is disclosed by the very step meant to protect it. Here no member +key ever appears on chain. ### Relays, and never holding users hostage A member who pays their own fee signs with their own wallet and destroys their own anonymity, so spends are relay-signed and the relay is paid out of `D`. -Two rules follow, both of which a competing submission violates: +Two rules follow, and both are easy to lose by accident: - **Relaying is permissionless.** Any key may relay. There is no single immutable - authority whose loss freezes the pool. (In the competing design the pool - authority is baked into the PDA seeds with no rotation instruction, so the - advertised "gasless rotating relay" cannot be deployed at all.) + authority whose loss freezes the pool. Baking a relay authority into the PDA + seeds is the tempting shortcut, and it is a trap: seeds cannot be changed, so + without a rotation instruction the authority is permanent and its loss is + terminal. - **There is always an exit.** A member may always self-spend, paying their own fee and accepting the privacy loss, so funds are never hostage to a relay's liveness. @@ -135,8 +140,8 @@ Two rules follow, both of which a competing submission violates: Entry fees accumulate in a reward pool paid pro-rata to *dwell* — how long a note stayed unspent — which is the behaviour that makes everyone else's set larger. Payouts are capped per epoch and gated on the `k` floor, so a lone participant -cannot recycle their own fee back to themselves, which is a live flaw in one -competitor's implementation and asserted by its own test. +cannot recycle their own fee back to themselves — a reward scheme that pays out +without a crowd gate is not an incentive, it is a refund with extra steps. ### Provenance measurement @@ -169,23 +174,22 @@ pool, epoch, frontier accumulator with root history, nullifier PDAs, tickets. *Done when* the suite runs against the built `.so` and covers the accounting invariant, replay rejection, `k`-floor rejection, action-binding mismatch, -malformed input on every handler, and a drain attempt of the shape that breaks -the competing implementations. CI green on fmt, clippy, tests and `build-sbf`. +malformed input on every handler, and a drain attempt of each shape described +above. CI green on fmt, clippy, tests and `build-sbf`. ### Day 3 — measurement, CLI, setup ceremony `mirror-provenance` against real mainnet data with the sample committed. The CLI end to end: keygen, deposit, prove, spend, claim, and an `audit` command that recomputes effective-`k` from committed data. Setup with a transcript that binds the **full** verifying key and a circuit digest, and a `verify-setup` any third -party can run — closing the gap where a competitor's ceremony verifier checks -only `delta` and would green-light a verifying key belonging to a different -circuit. +party can run — checking every element, because a verifier that compares only +`delta` would green-light a verifying key belonging to a different circuit. ### Day 4 — deploy, evidence, submission Devnet soak producing signatures for every flow plus on-chain negative cases with -their error codes. Mainnet deployment — no competitor has their own pool on -mainnet. Documentation: README, ARCHITECTURE, THREAT_MODEL with honest limits, -PROOF, EFFECTIVE_K. Open the PR with time for CI to finish, and submit on Earn. +their error codes, then mainnet deployment. Documentation: README, ARCHITECTURE, +THREAT_MODEL with honest limits, PROOF, EFFECTIVE_K. Open the PR with time for +CI to finish, and submit on Earn. ### Cut lines, in the order things get dropped @@ -193,7 +197,7 @@ Multi-party ceremony support degrades to a reproducible single-contributor setup with the multi-party path documented. Dwell rewards degrade to a flat entry fee. The provenance sample shrinks before its method weakens. **The accounting invariant, the negative tests, the honest limitations section and a green CI are -never cut** — they are what separates this from the submissions it means to beat. +never cut** — they are the difference between a privacy tool and a demo of one. ### What actually got cut, and why @@ -224,9 +228,9 @@ a decision rather than an overrun: ### Stretch, only if the above is complete A composability contribution to `account-cooker`, funding an agent fleet through -mirror-pool so that every agent wallet shares one provenance class. That repo's -strongest submission admits the observable common-funder graph is what defeats -it, and names a shielded funding path as an explicit non-goal. It is a second +mirror-pool so that every agent wallet shares one provenance class. An agent +fleet is defeated by its common-funder graph long before its behaviour looks +wrong, and a shielded funding path is the piece that attacks it. It is a second prize with a separate champion, and the work is small once the pool exists. ## Risks @@ -242,6 +246,6 @@ we actually have rather than to the number we would like to report. **Mainnet deployment cost.** A program of this size is a few SOL to deploy. Devnet evidence is the fallback and is on the critical path regardless. -**Competitors keep iterating.** Two of the three updated their branches within -the last day. We do not race their line count; we ship the thing all of them -declare open, and we make every number checkable. +**The field moves while we build.** The response is not to race anyone's line +count. It is to ship the thing this space declares open and unsolved, and to +make every number in it checkable by someone who does not trust us. diff --git a/docs/PROVENANCE_METHOD.md b/docs/PROVENANCE_METHOD.md index 4083c3aa..c2b5574f 100644 --- a/docs/PROVENANCE_METHOD.md +++ b/docs/PROVENANCE_METHOD.md @@ -124,7 +124,9 @@ advertised `k` overstates the adversary's residual uncertainty by a factor of **The one monotonicity we may claim:** adding channels only refines the partition, so our number is an **upper bound on privacy against any strictly stronger adversary at the same label resolution**. It is *not* a bound in the label-resolution direction. -State both halves; the second half is what the competing submission got backwards. +State both halves. Stating only the first is the common error, and it inverts the +claim: it lets a number measured at coarse labels be read as a guarantee against +an adversary holding finer ones. --- @@ -337,13 +339,17 @@ number for our own system has not been validated. See §9.0. population is enumerable, **census it**; there is then zero sampling error and §3.4 does not apply. -> **Defect avoided.** The competing submission samples 30 depositors from a pool's most -> recent 300 signatures and then sets *advertised k = 30 = the sample size*. That is a -> category error: advertised k is the pool's member count, not the sample size. -> Measured **[M 2026-07-25]**: the pool in question emits ~1,000 signatures per 1.2 days, -> so a 300-signature scan covers **the most recent ≈10 hours** of pool activity. A -> 90-day census of the same pool is ≈75 pages of `getSignaturesForAddress`, ≈75,000 -> signatures — verified reachable by `before`-cursor paging **[M 2026-07-25]**. +> **Failure mode.** Sampling *n* depositors from a pool's most recent signatures and +> then setting *advertised k = n* is a category error that is easy to commit and hard +> to see afterwards: advertised k is the pool's member count, and the sample size is a +> property of the budget. Reporting the second as the first makes the anonymity claim +> a restatement of how long the collector ran. +> +> The scale involved, measured **[M 2026-07-25]**: an active pool emits ~1,000 +> signatures per 1.2 days, so a 300-signature scan covers **the most recent ≈10 hours** +> of pool activity. A 90-day census of the same pool is ≈75 pages of +> `getSignaturesForAddress`, ≈75,000 signatures — verified reachable by `before`-cursor +> paging **[M 2026-07-25]**. The census is affordable; the shortcut is not necessary. **P2 — population prior.** All addresses receiving a value credit ≥ threshold in window `W`. This estimates the class prior *any* pool inherits, and yields `ρ` and the @@ -361,10 +367,12 @@ extractor needs — at **2.73 MB/block vs 6.41 MB for `"full"`**, 1,056 transact PRNG (§3.5), `getBlock` each, extract all value edges. 1,000 sampled blocks ≈ 1M transactions ≈ 2.7 GB, ~1,000 calls. This is a genuine probability sample of chain time. -> **Defect avoided.** The other competing submission's entire n=1,181 sample spans **9 -> consecutive slots (~3.2 s of chain time)**, containing 822 distinct destinations, and -> is presented with binomial Wilson intervals as if it were an i.i.d. population sample. -> Sampling slots uniformly costs the same and fixes it. +> **Failure mode.** A sample drawn from *consecutive* slots is not a sample of chain +> time. An n=1,181 sample spanning 9 consecutive slots covers ~3.2 seconds of chain +> time; the large n makes it look powerful, and binomial Wilson intervals computed over +> it look rigorous, but the observations are neither independent nor representative of +> anything but that instant. Sampling slots uniformly across the window costs the same +> number of calls and fixes it, so there is no efficiency argument for the shortcut. ### 3.3 Time stratification @@ -424,10 +432,12 @@ Record `S`, `blockhash(S)`, and the derivation rule in the manifest. ### 3.6 Never filter the frame on the outcome -> **Defect avoided.** The competing sampler discards every candidate depositor with -> ≥1,000 signatures — exactly the wallets with deep, traceable histories — and then -> reports 63 % of the remainder as untraceable. The exclusion criterion is correlated -> with the outcome. +> **Failure mode.** Discarding every candidate depositor with ≥1,000 signatures looks +> like a reasonable cost control — those are the expensive ones to walk — but it drops +> exactly the wallets with deep, traceable histories. Whatever remains is then +> disproportionately untraceable, and a headline like "63 % untraceable" is measuring +> the exclusion rule. **An exclusion criterion correlated with the outcome is not a +> filter, it is the finding.** **Rule: nothing is excluded from the frame.** High-activity members are stratified and reported, never dropped. If a member turns out to be a relayer or a program, that is a @@ -511,11 +521,13 @@ clean ("who created this wallet"), and one RPC call for most addresses. at 1,000 **[V]**. So for an address with fewer than 1,000 lifetime signatures, the **last element of the first page** is the birth transaction. -> **Defect avoided.** The competing tracer scans the **six most recent** transactions of -> each address. Funding is by definition among an address's *oldest* transactions. For -> any address with more than six transactions it is reading the wrong end of the -> history, which alone manufactures "unresolved" for active wallets. This is not -> disclosed anywhere in that submission's documentation. +> **Failure mode.** Scanning an address's most recent transactions to find its funder. +> `getSignaturesForAddress` returns newest-first, so taking the first handful is the +> path of least resistance — and funding is by definition among an address's *oldest* +> transactions. For any address with more history than the scan window, this reads the +> wrong end of the record, and it fails **silently**: the output is "unresolved", which +> is indistinguishable from a genuinely unresolvable wallet. The bias falls hardest on +> active wallets, which are the ones a provenance study most needs to resolve. **Independent corroboration of the rule.** Dune Spellbook contains `addresses_events_solana.first_funded_by`, described in its own `schema.yml` as *"Table @@ -566,12 +578,17 @@ rule can be ablated in the sensitivity table (§5.5). **`busy-unlabelled` is not "an attributable origin". Never call it one.** -> **Defect avoided.** In the competing tracer, `HUB_THRESHOLD == SIG_LIMIT == 1000`, so -> "reaches an attributable origin" literally means "**the address hit the RPC page -> cap**". That admits every DEX program, AMM vault, MEV bot and staking pool, and -> excludes a genuine CEX withdrawal address with 800 transactions. Our R4 decouples the -> hub test from the page cap by measuring the count with bounded paging, and requires -> corroboration from R1/R2/R3/R5 before any class is called an entity. +> **Failure mode.** Setting `HUB_THRESHOLD == SIG_LIMIT` — one constant for "how many +> signatures make this a hub" and "how many signatures will we fetch" — is a natural +> collapse, because both answer the same-sounding question. It makes "reaches an +> attributable origin" mean literally "**the address hit the RPC page cap**", which +> admits every DEX program, AMM vault, MEV bot and staking pool as an origin, while +> excluding a genuine CEX withdrawal address with 800 transactions. The two constants +> answer different questions and must be allowed to disagree. +> +> R4 here decouples the hub test from the page cap by measuring the count with bounded +> paging, and requires corroboration from R1/R2/R3/R5 before any class is called an +> entity. ### 4.6 Pagination handled honestly @@ -611,8 +628,11 @@ actual population rather than assuming. ## 5. Label tiers and multi-resolution reporting -This section is the answer to the structural weakness in the competing metric. Specify -it fully; it is our differentiator. +A single-resolution number is the structural weakness of most provenance metrics: +report one figure and it is unclear whether it describes an adversary who sees raw +addresses or one who sees named entities, and those differ by orders of magnitude. +Reporting the full ladder is what makes the claim falsifiable, so this section is +specified in full. ### 5.1 Four resolutions, strictly nested @@ -677,8 +697,9 @@ effect of each assumption is reported separately."* signer. Exchanges pay withdrawal fees from a small set of fee-payer accounts regardless of which hot wallet sources the value. Clustering on fee payer **collapses an exchange's hot wallets into one entity for free, with no third-party label list.** - This is the Solana-specific unlock and it is precisely the merge the competing metric - fails to make. + This is the Solana-specific unlock, and it is the merge most easily missed: without + it an exchange appears as a dozen unrelated classes, which inflates the measured + class count and flatters the pool. - **Co-signer clustering.** A transaction with signers `{A, B}` means one party controls both. - **Sweep clustering.** If addresses `A_1 … A_n` each send their full balance to `H`, @@ -780,15 +801,23 @@ the first four rows. **Principle: an infrastructure limit must never read as an absence of evidence.** -> **Defect avoided.** In the competing tracer, `sig_count()` is `.unwrap_or(0)` and -> `signatures()` is `.unwrap_or_default()`. A rate-limited call therefore makes an -> address look like it has *zero* signatures — so it is not detected as a hub and is -> admitted to the sample — and makes a traced address yield no funders, so the BFS -> terminates and the member is classified **unresolved**. Measured 429 behaviour (§8.1) -> shows that submission's 130 ms inter-call pacing is far above what the endpoint -> tolerates, so its large unresolved bucket is exactly what systematic rate limiting -> would manufacture. It does count failures and prints a warning, but the metric function -> never consults the count before reporting, and the published headline does not state it. +> **Failure mode.** `.unwrap_or(0)` on a signature count and `.unwrap_or_default()` on a +> signature list. Both are the idiomatic way to keep a traversal running past an error, +> and together they are silently catastrophic: a rate-limited call makes an address look +> like it has *zero* signatures, so it escapes hub detection and is admitted to the +> sample; and it makes a traced address yield no funders, so the walk terminates and the +> member is recorded **unresolved**. +> +> The result is that **an infrastructure failure is laundered into a data point**, and +> in the direction that inflates the unresolved bucket. Given the 429 behaviour measured +> in §8.1, aggressive inter-call pacing against a metered endpoint produces exactly the +> large unresolved bucket that systematic rate limiting would manufacture — which is +> indistinguishable, in the output, from a genuinely hard-to-trace population. +> +> Counting the failures and printing a warning is not sufficient. If the metric function +> never consults the count before reporting, the headline is still unconditioned on +> whether the run worked. Here the count gates the headline (§6) rather than annotating +> it. ### 6.1 Startup preconditions (hard) @@ -1019,7 +1048,9 @@ honestly demonstrate it. ### 9.0 The anti-pattern, stated so it cannot be repeated -A competing submission's effective-k harness contains, inside the metric: +The tautological metric is the defining failure of this genre, and it is worth writing +out because it does not look like cheating while you are writing it. The shape is a +harness that branches on the scenario label *inside* the metric: ```rust if scenario == Scenario::MirrorPool { @@ -1027,11 +1058,17 @@ if scenario == Scenario::MirrorPool { } ``` -Everything downstream is an identity: the posterior is `vec![1.0; k]` and -`2^{H(Uniform(k))} = k`. The published table reports the favourable column as exactly -16.00 / 32.00 / 64.00 against a ragged baseline, and a unit test pins the tautology to -1e-6 as a regression guard. The data structure *contains* `k` distinct funding roots for -that scenario; the code branches on the scenario label and discards the field. +Everything downstream is then an identity: the posterior is `vec![1.0; k]` and +`2^{H(Uniform(k))} = k`. The favourable column comes out as exactly 16.00 / 32.00 / +64.00 against a ragged baseline, and a unit test pinning that to 1e-6 reads as a +regression guard while actually pinning the tautology in place. The damning detail is +that the data structure typically *contains* the k distinct funding roots — the code +branches on the label and discards the field it should have read. + +It is worth being precise about why this happens, because "they were dishonest" is the +least useful explanation. A harness is built scenario by scenario; the favourable +scenario is stubbed first to get the plumbing running; the stub returns the answer the +author expects; and nothing downstream ever fails, because a tautology cannot fail. **Three tells a reviewer can check in thirty seconds**, and which we must never produce: @@ -1258,8 +1295,9 @@ not of members**; Möser et al. (PoPETs 2018, Monero) ring size 11 → effective **Do not cite arXiv:2510.09433** (Cristodaro, Kraner & Tessone, Tornado Cash cross-chain clustering). It was **withdrawn** at v3 on 2025-11-18: *"This paper has been withdrawn by -the author due to mistakes in the references"* **[V]**. Its numbers circulate widely in -search results and a competing submission relies on it. +the author due to mistakes in the references"* **[V]**. Its numbers still circulate +widely in search results and are easy to pick up second-hand, which is exactly why the +withdrawal is recorded here rather than the paper simply being left uncited. **Terminology.** "Provenance class" is not standard. Define it once against the established vocabulary: *"we partition the anonymity set into* **provenance classes**, diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 0ff6ddde..fe6927f1 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -76,9 +76,12 @@ digest it should print is b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7 -Checking the whole key matters: a verifier that compared only `delta`, as one -competing ceremony does, would certify a key belonging to an entirely different -circuit. Reproducibility is worth having and it is not security. +Checking the whole key matters, and it is a place where a ceremony verifier is +easy to get subtly wrong: comparing only `delta` — the element a contribution +actually changes — leaves `alpha`, `beta`, `gamma` and the `IC` vector +unchecked, so the verifier would happily certify a key belonging to an entirely +different circuit. This one compares every element. Reproducibility is worth +having and it is not security. Production needs a multi-party ceremony. The scaffolding for one is not in this submission and we do not claim it is. diff --git a/programs/mirror-pool/src/error.rs b/programs/mirror-pool/src/error.rs index a1b36f9e..34cbb113 100644 --- a/programs/mirror-pool/src/error.rs +++ b/programs/mirror-pool/src/error.rs @@ -37,8 +37,8 @@ pub enum MirrorProgramError { /// The vault does not hold enough to cover every unspent note. /// /// This is the accounting invariant. It is checked rather than assumed - /// because the two competing implementations are both drainable at exactly - /// this point. + /// because both standard drains in this design space surface exactly here, + /// and an invariant that is only assumed is not an invariant. InsolventVault = 13, /// The spend account has the wrong length or an unrecognised version. InvalidSpendAccount = 14, diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index d4f546a8..2570c326 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -124,9 +124,10 @@ fn create_pda_account<'a>( /// /// Permissionless: the first caller for a denomination creates the crowd /// everyone else joins. There is no privileged authority, so there is no key -/// whose loss freezes the pool — a competing implementation bakes its authority -/// into the pool's PDA seeds with no rotation instruction, which makes its -/// advertised rotating relay undeployable and its escrow permanently hostage. +/// whose loss freezes the pool. Baking an authority into the pool's PDA seeds is +/// the tempting alternative and it is a one-way door: seeds cannot change, so +/// without a rotation instruction that authority is permanent, a rotating relay +/// becomes undeployable, and the escrow is hostage to a single key forever. fn init_pool( program_id: &Pubkey, accounts: &[AccountInfo], @@ -190,11 +191,11 @@ fn init_pool( /// Escrows exactly one denomination and appends a note commitment. /// /// The escrowed amount is read from the pool, never from the instruction, so a -/// deposit cannot claim a size the pool did not set. This is the half of the -/// accounting invariant that a competing implementation is missing: there, the -/// escrowed lamports are a caller-supplied parameter that is never bound to the -/// hidden commitment, so a depositor of one lamport can later withdraw the whole -/// pool with an entirely valid proof. +/// deposit cannot claim a size the pool did not set. This is the ingress half of +/// the accounting invariant, and it is the half most easily left out: take the +/// escrowed lamports as a caller-supplied parameter without binding them to the +/// hidden commitment, and a depositor of one lamport can later withdraw the +/// whole pool with an entirely valid proof. /// /// Duplicate commitments are not rejected. Doing so would cost a marker account /// per deposit, and the only party a duplicate harms is whoever submitted it: diff --git a/programs/mirror-pool/src/state.rs b/programs/mirror-pool/src/state.rs index 6be13300..375e722f 100644 --- a/programs/mirror-pool/src/state.rs +++ b/programs/mirror-pool/src/state.rs @@ -18,9 +18,10 @@ use mirror_core::{Field, TREE_DEPTH, ZERO_LADDER}; /// chain; by the time a relay lands the transaction, later deposits may have /// advanced it. The ring is how long a proof stays valid. /// -/// 128 is deliberate. A competing implementation uses 32 *and* appends two -/// leaves per spend, so a proof there ages out after about sixteen operations -/// and fails under any real load. +/// 128 is deliberate, and the sizing interacts with how many leaves an +/// operation appends. A 32-entry ring against a design that appends two leaves +/// per spend gives a proof a useful life of about sixteen operations, which +/// holds in a test and collapses under any real load. pub const ROOT_HISTORY: usize = 128; /// Byte offsets. Kept together so the layout can be audited as a unit. @@ -283,9 +284,10 @@ impl<'a> Pool<'a> { /// The accounting invariant of the whole protocol. Because the denomination /// is a pool constant rather than a hidden field, the amount owed is a /// function of two counters and cannot be influenced by anything a prover - /// supplies. The two competing implementations are drainable precisely - /// because they lack this: one escrows an amount never bound to its - /// commitment, the other pays out once per epoch forever against one deposit. + /// supplies. Without an invariant of this shape a shielded pool is drainable + /// in one of two standard ways: an escrowed amount never bound to its + /// commitment, or an epoch-scoped nullifier that lets one deposit pay out + /// once per epoch forever. pub fn required_vault_lamports(&self) -> Result { self.outstanding_notes()? .checked_mul(self.denomination()) @@ -492,9 +494,9 @@ mod tests { assert_eq!(pool.required_vault_lamports().unwrap(), 3_000_000); } - /// The shape of the drain that breaks both competing implementations: more - /// payouts than deposits. Here it is not a matter of catching it late in the - /// spend path — the counter itself refuses. + /// The shape both standard drains converge on: more payouts than deposits. + /// Here it is not a matter of catching it late in the spend path — the + /// counter itself refuses. #[test] fn spending_more_notes_than_were_deposited_is_refused() { let mut data = initialised(1_000); From 9d844134729d557b4224e3340439b24b24f9198e Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 00:07:23 -0300 Subject: [PATCH 34/83] fix: delete two errors that could never fire, and one magic index Three findings from the review pass, all in the same category: code that describes a check it does not perform. MirrorError::BadPathLength was never constructed. A MerkleProof carries siblings: [Field; TREE_DEPTH], so a wrong-length path is not constructible -- the type enforces it and the variant advertised a runtime check that does not exist. Deleted, with the reasoning recorded where it was. MirrorProgramError::WrongDepositAmount was likewise never constructed: a deposit's size is read from the pool account, never from the instruction, so no caller-supplied amount survives to disagree with it. Deleted. Discriminant 11 is retired rather than reassigned, so every code below it stays stable. Settlement read the vault bump as pool_data.get(2) -- a second, silent copy of the account layout living in the processor, and one that would happily answer from a pool account of the wrong version. It now goes through Pool::vault_bump_of, which applies the same length and version checks load does. That matters because the bump is what settlement signs the vault PDA with: a bump derived from a malformed account is a signature for a PDA nobody controls. 183 tests. --- crates/mirror-core/src/lib.rs | 8 +++-- programs/mirror-pool/src/error.rs | 13 +++++++-- programs/mirror-pool/src/processor.rs | 4 +-- programs/mirror-pool/src/state.rs | 42 +++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 8 deletions(-) diff --git a/crates/mirror-core/src/lib.rs b/crates/mirror-core/src/lib.rs index ec24b244..aecc06c3 100644 --- a/crates/mirror-core/src/lib.rs +++ b/crates/mirror-core/src/lib.rs @@ -31,8 +31,12 @@ pub enum MirrorError { Poseidon, #[error("the accumulator is full")] TreeFull, - #[error("merkle path length does not match the tree depth")] - BadPathLength, + // There is no `BadPathLength`. A `MerkleProof` carries + // `siblings: [Field; TREE_DEPTH]`, a fixed-size array, so a path of the + // wrong length is not constructible and the check has nothing to reject. + // The property is enforced by the type rather than by a runtime branch, and + // carrying an error variant for it would advertise a check that does not + // exist. #[error("leaf index is outside the tree")] LeafIndexOutOfRange, } diff --git a/programs/mirror-pool/src/error.rs b/programs/mirror-pool/src/error.rs index 34cbb113..af77127f 100644 --- a/programs/mirror-pool/src/error.rs +++ b/programs/mirror-pool/src/error.rs @@ -30,8 +30,15 @@ pub enum MirrorProgramError { PoseidonFailed = 9, /// The accumulator is full. TreeFull = 10, - /// The deposit did not carry exactly the pool's denomination plus fee. - WrongDepositAmount = 11, + // + // 11 is retired. It was `WrongDepositAmount`, and it was unreachable: a + // deposit's size is read from the pool account, never from the instruction, + // so there is no caller-supplied amount left to disagree with. Deleting the + // variant rather than leaving it unconstructed keeps the enum an accurate + // list of what this program can actually reject. The discriminant is left + // unused rather than reassigned, so every code below stays stable for + // anything already reading them. + // /// Arithmetic overflowed or underflowed. ArithmeticOverflow = 12, /// The vault does not hold enough to cover every unspent note. @@ -79,7 +86,7 @@ impl From for MirrorProgramError { E::NonCanonicalField | E::BadFieldLength => MirrorProgramError::NonCanonicalField, E::Poseidon => MirrorProgramError::PoseidonFailed, E::TreeFull => MirrorProgramError::TreeFull, - E::BadPathLength | E::LeafIndexOutOfRange => MirrorProgramError::MalformedInstruction, + E::LeafIndexOutOfRange => MirrorProgramError::MalformedInstruction, } } } diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 2570c326..b4893fb1 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -652,9 +652,7 @@ fn invoke_action<'a>( }; let vault_bump = { let pool_data = pool_account.try_borrow_data()?; - *pool_data - .get(2) - .ok_or(MirrorProgramError::InvalidPoolAccount)? + Pool::vault_bump_of(&pool_data)? }; // The pool never invokes itself. Doing so would let a member craft a payload diff --git a/programs/mirror-pool/src/state.rs b/programs/mirror-pool/src/state.rs index 375e722f..e6c444e7 100644 --- a/programs/mirror-pool/src/state.rs +++ b/programs/mirror-pool/src/state.rs @@ -118,6 +118,22 @@ impl<'a> Pool<'a> { pub fn vault_bump(&self) -> u8 { self.data[offset::VAULT_BUMP] } + + /// The vault bump, read from an account still under an immutable borrow. + /// + /// [`Pool::load`] needs `&mut [u8]` because one wrapper serves every + /// accessor, but settlement wants the bump while it holds the pool + /// immutably. Reading the byte through its named offset — behind the same + /// length and version checks `load` applies — keeps the layout owned by + /// this module. Reaching into the account with a literal index would put a + /// second, silent copy of the layout in the processor, and it would not + /// notice a pool account of the wrong version at all. + pub fn vault_bump_of(data: &[u8]) -> Result { + if data.len() != POOL_LEN || data[offset::VERSION] != POOL_VERSION { + return Err(MirrorProgramError::InvalidPoolAccount); + } + Ok(data[offset::VAULT_BUMP]) + } pub fn denomination(&self) -> u64 { read_u64!(self, offset::DENOMINATION) } @@ -330,6 +346,32 @@ mod tests { assert_eq!(pool.next_index(), 0); } + /// The immutable reader must agree with the borrowing accessor, and must + /// refuse what `load` refuses. Settlement signs the vault with whatever + /// this returns, so a reader that answered from a wrong-sized or + /// wrong-version account would produce a bump for a PDA nobody controls. + #[test] + fn the_immutable_vault_bump_reader_agrees_and_validates() { + let mut data = initialised(1_000); + assert_eq!( + Pool::vault_bump_of(&data).unwrap(), + Pool::load(&mut data).unwrap().vault_bump() + ); + + let short = vec![0u8; POOL_LEN - 1]; + assert!(matches!( + Pool::vault_bump_of(&short), + Err(MirrorProgramError::InvalidPoolAccount) + )); + + let mut wrong_version = initialised(1_000); + wrong_version[offset::VERSION] = POOL_VERSION.wrapping_add(1); + assert!(matches!( + Pool::vault_bump_of(&wrong_version), + Err(MirrorProgramError::InvalidPoolAccount) + )); + } + #[test] fn a_fresh_pool_starts_at_the_empty_tree_root() { let mut data = initialised(1); From 6527eb6dab42833a8e41be739bb93b4ce7fde2e8 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 00:21:34 -0300 Subject: [PATCH 35/83] fix(program): refuse a nonzero entry fee instead of documenting the trap Entry fees land on the pool account. The dwell-reward mechanism meant to pay them out was cut, and it cannot be added back cheaply: paying them to anyone needs an authority, and this program has none by design -- there is deliberately no key whose loss can freeze a pool. So any nonzero fee was lamports every depositor pays and nobody, ever, can recover. That was recorded in PLAN.md as a known loose end. It is not a loose end, it is an armed fund trap behind a paragraph, and permitting a state whose only defence is that the documentation warns about it is precisely the pattern this codebase rejects elsewhere. Pool::initialise now refuses any nonzero entry fee, so the state is unrepresentable. The old check only refused a fee at or above the denomination, which waved through every value that actually matters. The collection call in deposit is gone with it rather than left unreachable, and the field stays in the layout for a version that ships a real payout path. Nothing regresses: both the soak and the end-to-end suite already ran at a zero fee, so the fee path had never been exercised end to end at all. --- docs/PLAN.md | 15 ++++++-- docs/THREAT_MODEL.md | 13 +++++-- programs/mirror-pool/src/processor.rs | 15 ++++---- programs/mirror-pool/src/state.rs | 52 +++++++++++++++++++-------- 4 files changed, 66 insertions(+), 29 deletions(-) diff --git a/docs/PLAN.md b/docs/PLAN.md index a2c099ad..7d2085a8 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -143,6 +143,10 @@ Payouts are capped per epoch and gated on the `k` floor, so a lone participant cannot recycle their own fee back to themselves — a reward scheme that pays out without a crowd gate is not an incentive, it is a refund with extra steps. +> **Shipped instead:** none of this. The dwell mechanism was cut, and once it +> was, the fee funding it had no recipient — so the fee was cut as well, by +> refusing any nonzero value at pool creation. See *What actually got cut*. + ### Provenance measurement A separate crate collects real mainnet funding data and reports effective @@ -207,9 +211,14 @@ a decision rather than an overrun: - **`open_epoch` and epoch state.** Folded into `submit_spend` and `settle_epoch`, which need none: a spend records its own timestamp and settlement reads the clock. Four instructions instead of seven. -- **`claim_reward` and dwell rewards.** Cut to a flat entry fee, as the cut line - above anticipated. The fees accrue on the pool account and no instruction pays - them out, which is a known loose end rather than a feature. +- **`claim_reward`, dwell rewards, and the entry fee with them.** The rewards + were cut to a flat entry fee, as the cut line above anticipated — and then the + fee was cut too, because a fee with no payout is not a simplification, it is a + fund trap. Fees accrue on the pool account; no instruction pays them out; and + none can be added without an authority this program deliberately does not + have. `Pool::initialise` now refuses any nonzero entry fee, so the trap is + unrepresentable rather than documented. The field stays in the layout for a + version that ships a real payout path. - **`self_spend`.** Not built because it is not needed: a member relays for themselves at zero fee and settles their own batch after the timeout. Same exit, one fewer instruction, less attack surface. Pinned by diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index fe6927f1..b3b60dd9 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -138,9 +138,16 @@ is no confidential-value layer here. ### Small crowds `k_floor` is enforced against notes in the tree, and a pool whose deposits are -mostly Sybils of one actor has a large nominal `k` and a small real one. The -entry fee prices set inflation; it does not prevent it. The provenance -measurement is the honest reading of what the set is worth. +mostly Sybils of one actor has a large nominal `k` and a small real one. + +Nothing in this program prevents that, and the entry fee that was meant to price +it does not exist: it would have accrued on the pool account with no instruction +able to pay it out, so pool creation refuses any nonzero value. What remains as a +cost to a Sybil is the denomination itself, which is recoverable, plus rent and +fees, which are not — a weak deterrent, stated as one. + +The provenance measurement is the honest reading of what a set is actually worth, +and it is the reason this limitation is measurable rather than merely admitted. ### Not audited diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index b4893fb1..1d5fe2f8 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -223,25 +223,24 @@ fn deposit(program_id: &Pubkey, accounts: &[AccountInfo], commitment: [u8; 32]) // the insert, after lamports had already moved. let leaf = Field::from_bytes(commitment).map_err(MirrorProgramError::from)?; - let (denomination, entry_fee) = { + let denomination = { let mut data = pool_account.try_borrow_mut_data()?; let pool = Pool::load(&mut data)?; if *vault_account.key != vault_address(program_id, pool_account.key).0 { return Err(MirrorProgramError::InvalidPda.into()); } - (pool.denomination(), pool.entry_fee()) + pool.denomination() }; + // A depositor pays the denomination and nothing else. There is no fee + // transfer here because `Pool::initialise` refuses a nonzero entry fee — + // fees would accrue on the pool account with no instruction able to pay + // them out, and no authority that could be given one. Should a future + // version add a real payout path, the collection belongs here. solana_program::program::invoke( &system_instruction::transfer(depositor.key, vault_account.key, denomination), &[depositor.clone(), vault_account.clone(), system.clone()], )?; - if entry_fee > 0 { - solana_program::program::invoke( - &system_instruction::transfer(depositor.key, pool_account.key, entry_fee), - &[depositor.clone(), pool_account.clone(), system.clone()], - )?; - } let mut data = pool_account.try_borrow_mut_data()?; let mut pool = Pool::load(&mut data)?; diff --git a/programs/mirror-pool/src/state.rs b/programs/mirror-pool/src/state.rs index e6c444e7..878d5d6f 100644 --- a/programs/mirror-pool/src/state.rs +++ b/programs/mirror-pool/src/state.rs @@ -190,9 +190,22 @@ impl<'a> Pool<'a> { if !(MIN_K_FLOOR..=MAX_K_FLOOR).contains(&k_floor) { return Err(MirrorProgramError::InvalidParameter); } - // An entry fee at or above the denomination costs more to join than the - // note is worth. - if entry_fee >= denomination { + // The entry fee must be zero, and this is a deliberate refusal rather + // than an unfinished feature. + // + // Fees accrue on the pool account. The dwell-reward mechanism that was + // meant to pay them out was cut, and it cannot simply be added back: + // paying them to anyone requires an authority, and this program has + // none by design — there is deliberately no key whose loss freezes the + // pool. So a nonzero fee here would be lamports that every depositor + // pays and that no instruction, and no person, can ever recover. + // + // Permitting that and documenting it as a known gap would be leaving a + // fund trap armed behind a paragraph. The field stays in the layout + // because the accessor and the offsets are already load-bearing and a + // future version with a real payout path will want it; until that path + // exists, the only permitted value is zero. + if entry_fee != 0 { return Err(MirrorProgramError::InvalidParameter); } @@ -323,11 +336,7 @@ mod tests { let mut data = blank(); { let mut pool = Pool::load_uninitialised(&mut data).unwrap(); - // The fee has to stay below the denomination, so scale it rather - // than hardcoding one that only suits large pools. - let entry_fee = denomination / 100; - pool.initialise(254, 253, denomination, entry_fee, 4) - .unwrap(); + pool.initialise(254, 253, denomination, 0, 4).unwrap(); } data } @@ -339,7 +348,7 @@ mod tests { assert_eq!(pool.bump(), 254); assert_eq!(pool.vault_bump(), 253); assert_eq!(pool.denomination(), 100_000); - assert_eq!(pool.entry_fee(), 1_000); // 1% of 100_000 + assert_eq!(pool.entry_fee(), 0); assert_eq!(pool.k_floor(), 4); assert_eq!(pool.deposit_count(), 0); assert_eq!(pool.spend_count(), 0); @@ -470,15 +479,28 @@ mod tests { assert!(pool.initialise(1, 1, 1_000_000, 0, MIN_K_FLOOR).is_ok()); } + /// A nonzero entry fee is unrecoverable, so it is refused at creation + /// rather than documented as a caveat. + /// + /// Fees land on the pool account and no instruction pays them out; adding + /// one would require an authority this program deliberately does not have. + /// Any nonzero value is therefore lamports that depositors pay and nobody + /// can ever retrieve — including a value well below the denomination, which + /// is the case an "is the fee affordable" check would happily wave through. #[test] - fn an_entry_fee_worth_more_than_the_note_is_refused() { + fn a_nonzero_entry_fee_is_refused() { let mut data = blank(); let mut pool = Pool::load_uninitialised(&mut data).unwrap(); - assert!(matches!( - pool.initialise(1, 1, 1_000, 1_000, 4), - Err(MirrorProgramError::InvalidParameter) - )); - assert!(pool.initialise(1, 1, 1_000, 999, 4).is_ok()); + for fee in [1, 999, 1_000, u64::MAX] { + assert!( + matches!( + pool.initialise(1, 1, 1_000, fee, 4), + Err(MirrorProgramError::InvalidParameter) + ), + "entry fee {fee} must be refused: nothing can pay it out" + ); + } + assert!(pool.initialise(1, 1, 1_000, 0, 4).is_ok()); } #[test] From 6da01f4dfb794e535e5fd913f77c06520cceac2f Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 00:38:25 -0300 Subject: [PATCH 36/83] proof: regenerate the devnet evidence so its central claim is checkable The README said the vault "closed to its rent-exempt minimum to the lamport" and cited PROOF.md, and PROOF.md contained only signatures -- the interesting part of that sentence was the one thing the artifact did not show. The soak also reused a pool an earlier run had created, so init_pool had no signature and the creation step was undocumented. Both fixed by running against a fresh pool on a redeployed program: - init_pool is recorded, on a pool this run created. - A vault accounting table carries the lamports: 80,000,012 owed against 80,000,012 paid, resting on a rent-exempt floor of 890,880 with a remainder of zero. - The floor is read from the cluster via getMinimumBalanceForRentExemption rather than baked in as a constant. Rent parameters are chain state, and the number is being used to assert an exact close. - The soak now *asserts* the close and fails the run otherwise. It previously printed the balances and exited zero regardless, which is a soak that proves nothing. Also re-measured what the docs publish, since the program changed: submit_spend is 101,123 CU (was 97,860) and the four-way CPI settlement is 39,644. Corrected in README, ARCHITECTURE and GROTH16_INTEGRATION, along with two paragraphs that still described entry fees accruing on the pool account. --- README.md | 14 ++- crates/mirror-cli/src/chain.rs | 16 ++++ crates/mirror-cli/src/soak.rs | 120 ++++++++++++++++++++++++-- data/soak-notes.json | 2 +- docs/ARCHITECTURE.md | 22 +++-- docs/GROTH16_INTEGRATION.md | 2 +- docs/PROOF.md | 40 ++++++--- programs/mirror-pool/src/processor.rs | 4 +- 8 files changed, 188 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 6515f57d..b2c04400 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,13 @@ not say. There is a section below of things we deliberately do not claim. | | | |---|---| -| `programs/mirror-pool` | The on-chain program. `submit_spend`, proof and all, measured at **97,860 CU** on a real SVM. | +| `programs/mirror-pool` | The on-chain program. `submit_spend`, proof and all, measured at **101,123 CU** on a real SVM. | | `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain. | | `crates/mirror-circuit` | R1CS gadget, membership circuit, prover, key export. | | `crates/mirror-provenance` | The funding-provenance measurement. | | `crates/mirror-cli` | `setup`, `verify-setup`, `check-endpoint`, `seeds`, `collect`, `analyze`, `soak`. | -**182 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**183 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -78,7 +78,7 @@ The brief asks for an anonymity set for *behaviour*, not for funds. That distinction is load-bearing here, so it is tested rather than asserted: ``` -settled 4 real CPI actions in one transaction, 39,636 CU +settled 4 real CPI actions in one transaction, 39,644 CU ``` `a_crowd_of_members_perform_a_real_protocol_action_together` seeds a pool, @@ -232,7 +232,13 @@ Live on **devnet** at `8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa`. The whole lifecycle ran there against a real validator — pool creation, deposits, spends each carrying a Groth16 proof verified by the deployed program's own syscall, and a settlement that closed the vault to its rent-exempt minimum to the lamport. -Every signature is in `docs/PROOF.md`. + +`docs/PROOF.md` has every signature *and* the lamports, because "closed to the +rent-exempt minimum" is the interesting part of that sentence and a list of +signatures does not show it: 80,000,012 owed against 80,000,012 paid out, and a +vault resting on its floor with a remainder of zero. The soak asserts both and +fails the run otherwise, so that table cannot record a discrepancy and still +exit successfully. **Not on mainnet, and that is a decision rather than an omission.** The trusted setup here is reproducible, not secure: the seed is public, so the toxic waste is diff --git a/crates/mirror-cli/src/chain.rs b/crates/mirror-cli/src/chain.rs index 21a19106..eac7b657 100644 --- a/crates/mirror-cli/src/chain.rs +++ b/crates/mirror-cli/src/chain.rs @@ -162,4 +162,20 @@ impl Chain { .and_then(|b| b.as_u64()) .ok_or_else(|| anyhow!("balance missing")) } + + /// The rent-exempt floor for an account of `space` bytes. + /// + /// Asked of the cluster rather than computed here. The rent parameters are + /// chain state, so a constant baked into this binary would be a second + /// source of truth that is right until it is not — and the number is used + /// to assert that a vault settled to its floor exactly, which is a claim + /// worth grounding in what the cluster itself says. + pub fn rent_exempt_minimum(&self, space: usize) -> Result { + let v = self.call( + "getMinimumBalanceForRentExemption", + serde_json::json!([space, { "commitment": "confirmed" }]), + )?; + v.as_u64() + .ok_or_else(|| anyhow!("rent-exempt minimum missing")) + } } diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs index 0cee1c73..699094bb 100644 --- a/crates/mirror-cli/src/soak.rs +++ b/crates/mirror-cli/src/soak.rs @@ -22,9 +22,11 @@ use solana_program::{ use solana_signer::Signer; use solana_transaction::Transaction; -const DENOMINATION: u64 = 20_000_001; // 0.02 SOL; a pool is per-denomination, so - // this also picks out a fresh pool for the - // evidence run. +// A pool is unique per denomination, so this constant is also what selects the +// pool. Changing it starts a clean one, which is how the evidence run gets to +// record its own `init_pool` rather than reusing a pool an earlier run created +// and leaving the creation step undocumented. +const DENOMINATION: u64 = 20_000_003; // 0.02 SOL const ENTRY_FEE: u64 = 0; const K_FLOOR: u32 = 4; const RELAY_FEE: u64 = 200_000; @@ -36,6 +38,41 @@ pub struct Step { pub note: String, } +/// What the vault actually held, so the solvency claim is checkable from this +/// artifact instead of from a sentence asserting it. +/// +/// Saying "the vault closed to its rent-exempt minimum to the lamport" and then +/// publishing an evidence file containing only signatures asks a reader to take +/// the interesting part on trust. These are the numbers that sentence is about. +pub struct Accounting { + pub denomination: u64, + pub notes_settled: u64, + pub relay_fee: u64, + pub vault_before_settle: u64, + pub vault_after_settle: u64, + pub vault_rent_exempt_minimum: u64, +} + +impl Accounting { + /// What settlement was obliged to disburse: the full denomination for each + /// note, which the relay fee is taken out of rather than added to. + pub fn expected_payout(&self) -> u64 { + self.denomination.saturating_mul(self.notes_settled) + } + + pub fn observed_payout(&self) -> u64 { + self.vault_before_settle + .saturating_sub(self.vault_after_settle) + } + + /// Whether the vault came to rest exactly on its rent-exempt floor, with + /// every note paid and not one lamport more. + pub fn closed_exactly(&self) -> bool { + self.observed_payout() == self.expected_payout() + && self.vault_after_settle == self.vault_rent_exempt_minimum + } +} + pub struct Soak { client: Chain, program_id: Pubkey, @@ -44,6 +81,7 @@ pub struct Soak { vault: Pubkey, pub steps: Vec, pub negatives: Vec<(String, String)>, + pub accounting: Option, } impl Soak { @@ -58,6 +96,7 @@ impl Soak { vault, steps: Vec::new(), negatives: Vec::new(), + accounting: None, } } @@ -319,6 +358,10 @@ impl Soak { pub fn balance(&self, key: &Pubkey) -> Result { self.client.balance(key) } + + pub fn rent_exempt_minimum(&self, space: usize) -> Result { + self.client.rent_exempt_minimum(space) + } pub fn program_id(&self) -> Pubkey { self.program_id } @@ -416,10 +459,35 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re let vault_before = soak.balance(&soak.vault())?; soak.settle(&batch)?; let vault_after = soak.balance(&soak.vault())?; + // The vault carries no data, so its floor is the rent-exempt minimum for a + // zero-byte account — asked of the cluster rather than assumed. + let vault_floor = soak.rent_exempt_minimum(0)?; + let accounting = Accounting { + denomination: DENOMINATION, + notes_settled: K_FLOOR as u64, + relay_fee: RELAY_FEE, + vault_before_settle: vault_before, + vault_after_settle: vault_after, + vault_rent_exempt_minimum: vault_floor, + }; println!( - " vault {vault_before} -> {vault_after} (paid out {})", - vault_before.saturating_sub(vault_after) + " vault {vault_before} -> {vault_after} (paid out {}, expected {}, floor {vault_floor})", + accounting.observed_payout(), + accounting.expected_payout(), ); + // Assert it here rather than only reporting it. A soak that prints an + // unexpected number and exits zero is a soak that proves nothing. + if !accounting.closed_exactly() { + return Err(anyhow!( + "vault did not close cleanly: paid out {} against an expected {}, \ + and came to rest at {} against a rent-exempt floor of {vault_floor}", + accounting.observed_payout(), + accounting.expected_payout(), + accounting.vault_after_settle, + )); + } + println!(" vault closed to its rent-exempt floor exactly"); + soak.accounting = Some(accounting); println!("\nnegative path:"); // A replayed proof: the nullifier record already exists. @@ -486,6 +554,48 @@ fn write_proof(soak: &Soak, url: &str, out: &std::path::Path) -> Result<()> { )); } + if let Some(a) = &soak.accounting { + md.push_str("\n## Vault accounting\n\n"); + md.push_str( + "The accounting invariant is a statement about the vault's lamports, so \ + here are the lamports. The vault holds escrow and carries no data, which \ + is what makes its floor the rent-exempt minimum for a zero-byte account \ + — read from the cluster during the run, not assumed.\n\n", + ); + md.push_str("| quantity | lamports |\n|---|---|\n"); + md.push_str(&format!("| denomination | {} |\n", a.denomination)); + md.push_str(&format!("| notes settled | {} |\n", a.notes_settled)); + md.push_str(&format!( + "| relay fee (taken out of the denomination, not added) | {} |\n", + a.relay_fee + )); + md.push_str(&format!( + "| vault before settlement | {} |\n", + a.vault_before_settle + )); + md.push_str(&format!( + "| vault after settlement | {} |\n", + a.vault_after_settle + )); + md.push_str(&format!( + "| rent-exempt minimum, 0 bytes | {} |\n", + a.vault_rent_exempt_minimum + )); + md.push_str(&format!("| **paid out** | **{}** |\n", a.observed_payout())); + md.push_str(&format!( + "| **owed** (denomination × notes) | **{}** |\n\n", + a.expected_payout() + )); + md.push_str(&format!( + "Paid out equals owed, and the vault came to rest on its floor with a \ + remainder of {} lamports. The soak asserts both and fails the run \ + otherwise, so this table cannot record a discrepancy and still exit \ + successfully.\n", + a.vault_after_settle + .saturating_sub(a.vault_rent_exempt_minimum) + )); + } + md.push_str("\n## Rejections\n\n"); md.push_str( "A negative case is only evidence if the program's own error code is what \ diff --git a/data/soak-notes.json b/data/soak-notes.json index 2887fb35..d05e0229 100644 --- a/data/soak-notes.json +++ b/data/soak-notes.json @@ -1 +1 @@ -[[1784979416922187,14135209596979],[1784979416922194,14135209596992],[1784979416922201,14135209597005],[1784979416922208,14135209597018],[1784979487922400,14135210159228],[1784979487922407,14135210159241],[1784979487922414,14135210159254],[1784979487922421,14135210159267]] \ No newline at end of file +[[1785041911109669,14135704486965],[1785041911109676,14135704486978],[1785041911109683,14135704486991],[1785041911109690,14135704487004]] \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 02d3e5af..41ba823b 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -73,9 +73,15 @@ vault *after* lamports move rather than inferred from the arithmetic that moved them, and the spend counter refuses to exceed the deposit counter outright. Escrow lives in its own vault PDA holding no data, so the invariant reads against -a balance containing nothing but escrow and its own rent. Entry fees accrue on -the pool account instead, where they can never be mistaken for lamports backing -an unspent note. +a balance containing nothing but escrow and its own rent. Nothing else is ever +credited to it, and pool creation refuses a nonzero entry fee, so there is no +second category of lamports anywhere that could be mistaken for backing for an +unspent note. + +`docs/PROOF.md` carries the devnet numbers: four notes settled, 80,000,012 +lamports owed and 80,000,012 paid, and a vault that came to rest on its +rent-exempt floor with a remainder of zero. The soak asserts that rather than +printing it, so a run that disagreed would fail instead of publishing. ## The circuit @@ -85,7 +91,7 @@ with root `R`, my nullifier is `H1(k)`, and this proof is bound to `action`.* Three public inputs, and that is a cost decision. On-chain verification measures as `74,179 + 5,661 × N` compute units, so each input costs about 5.7k CU. See `GROTH16_INTEGRATION.md`; the figure this repository reproduces directly is the -whole `submit_spend` instruction at 97,860 CU. +whole `submit_spend` instruction at 101,123 CU. | public input | why it cannot be a witness | |---|---| @@ -115,10 +121,10 @@ constants rather than re-deriving them. The gadget and the host are each checked against circomlib's published `poseidon([1,2])` vector rather than against each other, and the syscall is then checked against the host on-chain — the end-to-end suite asserts the root the -deployed program builds equals the root the host built. Several published Solana -projects ship a gadget whose native and in-circuit hashes are different -functions; that failure only appears at proving time, and this is the test that -catches it. +deployed program builds equals the root the host built. A gadget whose native and +in-circuit hashes are different functions is the classic failure here: nothing +catches it until proving time, and the symptom — proofs that verify nowhere — +points at everything except the hash. This is the test that catches it. A pure-Rust Poseidon on SBF overflows the 4 KB stack frame and costs roughly 1,500× the syscall even where codegen lets it complete, so no arkworks code is diff --git a/docs/GROTH16_INTEGRATION.md b/docs/GROTH16_INTEGRATION.md index ba049739..0aa7549d 100644 --- a/docs/GROTH16_INTEGRATION.md +++ b/docs/GROTH16_INTEGRATION.md @@ -58,7 +58,7 @@ than a slice. Measured in LiteSVM against a real SBF build, with a scratch circuit per input count that is not part of this repository. The figure this repository does -reproduce is the whole `submit_spend` instruction at three inputs — 97,860 CU, +reproduce is the whole `submit_spend` instruction at three inputs — 101,123 CU, printed by `cargo test -p mirror-pool-program --test end_to_end -- --nocapture`. | Public inputs | `verify()` | diff --git a/docs/PROOF.md b/docs/PROOF.md index 08b65698..30799af0 100644 --- a/docs/PROOF.md +++ b/docs/PROOF.md @@ -4,22 +4,40 @@ Every line here is a transaction that landed. Nothing is simulated: the proof is - cluster: `devnet` - program: `8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa` -- pool: `CoxF5A4YG6bgtZ55WTYpLkXXJWC8oFbpsxbmfhvMe14W` -- vault: `HCkWvxyREJiLByGHbKt47uXJxjTubDM3V2Fs1zjmPu2p` +- pool: `ABb58741sCZV6nWtwfK1dEqonLBp2dGDiAe85jBnWa22` +- vault: `5vWyEoKQjoYMUnyRe1yzSnGGUBfhs654ssxiFadwxB5t` ## Flows | step | signature | note | |---|---|---| -| deposit | [`4GCouwQ6xCierPJSoWWcpi7gq91k15YnuAbsQXMFXgK9XcSThsZ6xTamBcdt5R43o2LWUNNUDqosNpQFr48jtE9f`](https://explorer.solana.com/tx/4GCouwQ6xCierPJSoWWcpi7gq91k15YnuAbsQXMFXgK9XcSThsZ6xTamBcdt5R43o2LWUNNUDqosNpQFr48jtE9f?cluster=devnet) | note 5 | -| deposit | [`NidbwQVPYXtQ65pSs7i5GujuALQVbiKvwMz4mPahtURz9PQuz1qQ7K6ioYGwkYEKZ662LBo92NNHiQYCiJzbrXX`](https://explorer.solana.com/tx/NidbwQVPYXtQ65pSs7i5GujuALQVbiKvwMz4mPahtURz9PQuz1qQ7K6ioYGwkYEKZ662LBo92NNHiQYCiJzbrXX?cluster=devnet) | note 6 | -| deposit | [`2tkuqJAivXhhdqzgwHk432Pmz5WF7qUZbax5Pps4ufxeppnuiGSFYgxKXfvzfKht6phUEZ5pMDTKqMrY8SPwHz3j`](https://explorer.solana.com/tx/2tkuqJAivXhhdqzgwHk432Pmz5WF7qUZbax5Pps4ufxeppnuiGSFYgxKXfvzfKht6phUEZ5pMDTKqMrY8SPwHz3j?cluster=devnet) | note 7 | -| deposit | [`4amq6HyxccQtKPXcvpy6jNSQ8EhwxEybFA1f3sy8mFCBXy2PSrYcA5nnrQigiAMguEhVNMEsai3566DWP4UzhUuA`](https://explorer.solana.com/tx/4amq6HyxccQtKPXcvpy6jNSQ8EhwxEybFA1f3sy8mFCBXy2PSrYcA5nnrQigiAMguEhVNMEsai3566DWP4UzhUuA?cluster=devnet) | note 8 | -| submit_spend | [`2PDhAtCvDRFCW3u3nUaXvWSNjZkS72cdbNGoH1hAbpXoeGRnv4UcUCvnFMkscHnnW3M2kcFe722WieZApHmHrx8H`](https://explorer.solana.com/tx/2PDhAtCvDRFCW3u3nUaXvWSNjZkS72cdbNGoH1hAbpXoeGRnv4UcUCvnFMkscHnnW3M2kcFe722WieZApHmHrx8H?cluster=devnet) | note 4, relay-signed | -| submit_spend | [`2MoYdaFjZE3GjW6QRdARzrQjprRyHALzC5uEyN7cY7PK4qyCsr2j5AJ7LVc17Ldydk4UNhMR6Eh7TZnKCiydyS4L`](https://explorer.solana.com/tx/2MoYdaFjZE3GjW6QRdARzrQjprRyHALzC5uEyN7cY7PK4qyCsr2j5AJ7LVc17Ldydk4UNhMR6Eh7TZnKCiydyS4L?cluster=devnet) | note 5, relay-signed | -| submit_spend | [`2d5D67BzdtuPzpXEAZFqK6MCQr92Zn7TH5ynCvuVP5mhacNX7cpZ7o3w4CfzfK1DbRrzHnqiR1xN4az1xtHRu6Zw`](https://explorer.solana.com/tx/2d5D67BzdtuPzpXEAZFqK6MCQr92Zn7TH5ynCvuVP5mhacNX7cpZ7o3w4CfzfK1DbRrzHnqiR1xN4az1xtHRu6Zw?cluster=devnet) | note 6, relay-signed | -| submit_spend | [`413vqoRynW8WzAFaHuhGx8hrxTz9GcwWf5c5EPjig8Lkx4DUJyXA2RdoF7HhaMRgubciy8fr7tT1zM4UHe8rRdGd`](https://explorer.solana.com/tx/413vqoRynW8WzAFaHuhGx8hrxTz9GcwWf5c5EPjig8Lkx4DUJyXA2RdoF7HhaMRgubciy8fr7tT1zM4UHe8rRdGd?cluster=devnet) | note 7, relay-signed | -| settle_epoch | [`5WrDG3EoU2kwVe1rEdkiQ8tNP1YbxAkKobquV8QSQoJLJn6qCQg1pbAixSmpzDvJxPcXXtFx3fUWnpdrprbkrAZj`](https://explorer.solana.com/tx/5WrDG3EoU2kwVe1rEdkiQ8tNP1YbxAkKobquV8QSQoJLJn6qCQg1pbAixSmpzDvJxPcXXtFx3fUWnpdrprbkrAZj?cluster=devnet) | 4 spends | +| init_pool | [`a1Um5GdackChmKVUNUgneYCEFiqQ4hjcqVQFhb9tTeAiYNQBRKCbgCJf8TAYXWP877b95ZDAxcebJWrbrnSVRFm`](https://explorer.solana.com/tx/a1Um5GdackChmKVUNUgneYCEFiqQ4hjcqVQFhb9tTeAiYNQBRKCbgCJf8TAYXWP877b95ZDAxcebJWrbrnSVRFm?cluster=devnet) | denomination 20000003, k_floor 4 | +| deposit | [`5ZJhBMLfajJBxfxeHgArQ25uHx6XZDp8PH44W4wuE4g15yexTmiC6oJJXfaLBHu9MLXRD3ggwEpB6FVpTMWvV8Py`](https://explorer.solana.com/tx/5ZJhBMLfajJBxfxeHgArQ25uHx6XZDp8PH44W4wuE4g15yexTmiC6oJJXfaLBHu9MLXRD3ggwEpB6FVpTMWvV8Py?cluster=devnet) | note 1 | +| deposit | [`3cXFiZSM2Z3onk31w8Q7Q21d56ZqrNACEQh4VdzLg2ub8aXURRVuZLL9TKqcnFWAtiftYRoGn9xxERx3RF21zVmX`](https://explorer.solana.com/tx/3cXFiZSM2Z3onk31w8Q7Q21d56ZqrNACEQh4VdzLg2ub8aXURRVuZLL9TKqcnFWAtiftYRoGn9xxERx3RF21zVmX?cluster=devnet) | note 2 | +| deposit | [`4HSKcvxtBnxSeECCEc2UPCfc4xoUcbiMminMET73A5mAwqPtjR5u2prLgRaxPMcWS18EZB5pbktKhAZdGLXB45LG`](https://explorer.solana.com/tx/4HSKcvxtBnxSeECCEc2UPCfc4xoUcbiMminMET73A5mAwqPtjR5u2prLgRaxPMcWS18EZB5pbktKhAZdGLXB45LG?cluster=devnet) | note 3 | +| deposit | [`4vXba4TPxyGJeZosLnaDddJom7yerWCVAGoMHQsT1RMwCS7NQ34rEbX1TqA6DUYUV3Pugj7SDY34DN8MaruHMVpP`](https://explorer.solana.com/tx/4vXba4TPxyGJeZosLnaDddJom7yerWCVAGoMHQsT1RMwCS7NQ34rEbX1TqA6DUYUV3Pugj7SDY34DN8MaruHMVpP?cluster=devnet) | note 4 | +| submit_spend | [`2AxhzG9wa3nVsuxNJf7WmhVYHNQw1QimrFvSdBKgz3ZyyTJbr9D4iDtzVq8Ap7f4vqVrKCcsWLabVUpEhiPmCu6R`](https://explorer.solana.com/tx/2AxhzG9wa3nVsuxNJf7WmhVYHNQw1QimrFvSdBKgz3ZyyTJbr9D4iDtzVq8Ap7f4vqVrKCcsWLabVUpEhiPmCu6R?cluster=devnet) | note 0, relay-signed | +| submit_spend | [`4n5zRhNC3GpmDAxoauNMsb9xm9FWDgqMMFGSKuB8176xBrAVPCUSx54uBZphVdHbUu3LZeNgXCgQCaUDorynKU73`](https://explorer.solana.com/tx/4n5zRhNC3GpmDAxoauNMsb9xm9FWDgqMMFGSKuB8176xBrAVPCUSx54uBZphVdHbUu3LZeNgXCgQCaUDorynKU73?cluster=devnet) | note 1, relay-signed | +| submit_spend | [`5TTZhv3kJ1EQpVjdh3nUZt5rgPHSXzfqWfyeGx97vj8XM4BJz1orxc6pYqiSirqGUWUbVkX6zvmiN65RcGcRp71v`](https://explorer.solana.com/tx/5TTZhv3kJ1EQpVjdh3nUZt5rgPHSXzfqWfyeGx97vj8XM4BJz1orxc6pYqiSirqGUWUbVkX6zvmiN65RcGcRp71v?cluster=devnet) | note 2, relay-signed | +| submit_spend | [`eiczViayoZXPJ5VbEoFSQT6Pu9udpZByo532RA7YTWfKSqoL7GqcuhiCxZmeyekSeyyRW8GKstzNCFLdajW4M7G`](https://explorer.solana.com/tx/eiczViayoZXPJ5VbEoFSQT6Pu9udpZByo532RA7YTWfKSqoL7GqcuhiCxZmeyekSeyyRW8GKstzNCFLdajW4M7G?cluster=devnet) | note 3, relay-signed | +| settle_epoch | [`44qJhqRE1rxkZqqbgnKoy1vZBoqa2P3dmH6zuwbQy4RgCp72XgUkWqqjiaNR9iUXp2gZndtcxMuLWsH4so6d2TiF`](https://explorer.solana.com/tx/44qJhqRE1rxkZqqbgnKoy1vZBoqa2P3dmH6zuwbQy4RgCp72XgUkWqqjiaNR9iUXp2gZndtcxMuLWsH4so6d2TiF?cluster=devnet) | 4 spends | + +## Vault accounting + +The accounting invariant is a statement about the vault's lamports, so here are the lamports. The vault holds escrow and carries no data, which is what makes its floor the rent-exempt minimum for a zero-byte account — read from the cluster during the run, not assumed. + +| quantity | lamports | +|---|---| +| denomination | 20000003 | +| notes settled | 4 | +| relay fee (taken out of the denomination, not added) | 200000 | +| vault before settlement | 80890892 | +| vault after settlement | 890880 | +| rent-exempt minimum, 0 bytes | 890880 | +| **paid out** | **80000012** | +| **owed** (denomination × notes) | **80000012** | + +Paid out equals owed, and the vault came to rest on its floor with a remainder of 0 lamports. The soak asserts both and fails the run otherwise, so this table cannot record a discrepancy and still exit successfully. ## Rejections diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 1d5fe2f8..24b3bee3 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -159,8 +159,8 @@ fn init_pool( let denom_le = denomination.to_le_bytes(); - // The pool account carries the accumulator and, above its own rent, the - // reward pool that entry fees accrue into. + // The pool account carries the accumulator and its own rent, and nothing + // else: entry fees are refused at initialisation, so nothing accrues here. create_pda_account( payer, pool_account, From 9e31d31710d044c0a6f7ba8efefa03d247dae0ac Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 00:52:05 -0300 Subject: [PATCH 37/83] proof: two more on-chain rejections, and say what each one establishes PROOF.md carried a single rejection and reported it as a bare hex code, which asks a reader to hold the error enum in their head to learn anything from it. Two cases added, both attacking the claim the README makes most loudly -- that a relay can neither redirect nor re-price an action. A genuine proof is generated for one beneficiary and one fee, and the instruction sent names a different beneficiary, then a tenfold fee. The binding is recomputed on-chain from what the instruction says, so both fail the pairing: 0x12, from the deployed program on a real validator. Both attack a note that is still live. That is the point of depositing a spare one after settlement rather than reusing a spent note: against a spent note the replay guard fires first, the case passes, and it proves nothing about the check under test. That is how a negative test comes to pass for the wrong reason, and the artifact now says so. Each rejection also records what it establishes, so the table is readable without the error enum to hand. Fixed a claim in the README that a reader could have falsified in one command: it said the committed sample included the run that exceeded the 1% RPC-failure limit. It does not -- the committed samples are Runs 3 and 4, both clean. The two runs that produced no headline have no committed sample, and the README now says that instead. --- README.md | 23 +++--- crates/mirror-cli/src/soak.rs | 138 +++++++++++++++++++++++++++------- data/soak-notes.json | 2 +- docs/ARCHITECTURE.md | 4 +- docs/PROOF.md | 43 ++++++----- docs/THREAT_MODEL.md | 21 +++++- 6 files changed, 171 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index b2c04400..6d80e4d3 100644 --- a/README.md +++ b/README.md @@ -176,14 +176,19 @@ mirror analyze --sample sample.json # pure, offline, deterministic ``` `data/sample-privacycash-run4.json` is the committed artifact behind the -headline, and `data/sample-privacycash.json` is the earlier run it is compared -against. Both are committed, so anyone holding them recomputes the numbers -without RPC access and without trusting that our endpoint behaved the same way -on their machine — including the run that came back above the 1% RPC-failure -limit and so yielded no headline at all. `docs/MEASUREMENT_LOG.md` records every -run, and Run 4's budget and predictions were committed to git *before* the -collection started, so the parameters are a declaration rather than a -description. +headline, and `data/sample-privacycash.json` is Run 3, the earlier and smaller +budget it is compared against. Both are committed, so anyone holding them +recomputes both numbers without RPC access and without trusting that our +endpoint behaved the same way on their machine — including the comparison that +shows coverage *falling* as resolution rose. + +`docs/MEASUREMENT_LOG.md` records every run, including the two that produced no +headline at all: one whose frame was size-biased and resolved nothing, and one +that came back above the 1% RPC-failure limit. Those two have no committed +sample, and saying so is part of the record — what is committed is what the +published numbers come from. Run 4's budget and its predictions were committed +to git *before* the collection started, so its parameters are a declaration +rather than a description. ### Design choices, and the failure each one exists to avoid @@ -235,7 +240,7 @@ a settlement that closed the vault to its rent-exempt minimum to the lamport. `docs/PROOF.md` has every signature *and* the lamports, because "closed to the rent-exempt minimum" is the interesting part of that sentence and a list of -signatures does not show it: 80,000,012 owed against 80,000,012 paid out, and a +signatures does not show it: 80,000,028 owed against 80,000,028 paid out, and a vault resting on its floor with a remainder of zero. The soak asserts both and fails the run otherwise, so that table cannot record a discrepancy and still exit successfully. diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs index 699094bb..859eff87 100644 --- a/crates/mirror-cli/src/soak.rs +++ b/crates/mirror-cli/src/soak.rs @@ -26,7 +26,7 @@ use solana_transaction::Transaction; // pool. Changing it starts a clean one, which is how the evidence run gets to // record its own `init_pool` rather than reusing a pool an earlier run created // and leaving the creation step undocumented. -const DENOMINATION: u64 = 20_000_003; // 0.02 SOL +const DENOMINATION: u64 = 20_000_007; // 0.02 SOL const ENTRY_FEE: u64 = 0; const K_FLOOR: u32 = 4; const RELAY_FEE: u64 = 200_000; @@ -80,7 +80,8 @@ pub struct Soak { pool: Pubkey, vault: Pubkey, pub steps: Vec, - pub negatives: Vec<(String, String)>, + /// Case name, the program's own error code, and what the rejection proves. + pub negatives: Vec<(String, String, String)>, pub accounting: Option, } @@ -116,7 +117,7 @@ impl Soak { }); } - fn record_negative(&mut self, name: &str, err: String) { + fn record_negative(&mut self, name: &str, proves: &str, err: String) { // Only the program's own error code matters here; the rest is noise. // Take only the hex code. The RPC embeds the error inside a JSON blob, // so splitting on whitespace drags the rest of the payload along and the @@ -132,7 +133,8 @@ impl Soak { .filter(|s| !s.is_empty()) .unwrap_or_else(|| "(no custom code)".to_string()); println!(" {name:<28} rejected with {code}"); - self.negatives.push((name.to_string(), code)); + self.negatives + .push((name.to_string(), code, proves.to_string())); } fn account_meta_pool(&self) -> Vec { @@ -312,13 +314,14 @@ impl Soak { pub fn expect_rejection( &mut self, name: &str, + proves: &str, ix: Instruction, signers: &[&Keypair], ) -> Result<()> { match self.send(ix, signers) { Ok(sig) => Err(anyhow!("{name} was accepted: {sig}")), Err(e) => { - self.record_negative(name, e.to_string()); + self.record_negative(name, proves, e.to_string()); Ok(()) } } @@ -415,6 +418,39 @@ fn read_keypair(path: &str) -> Result { Keypair::try_from(&bytes[..]).map_err(|e| anyhow!("reading {expanded}: {e}")) } +/// Proves membership of one note, bound to one beneficiary and one relay fee. +/// +/// Kept separate so a negative case can prove an honest statement and then send +/// a *different* one. That is the whole shape of the redirect attack: the proof +/// is genuine, and what the relay changes is the instruction around it. +fn proof_for_note( + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + beneficiary: &Pubkey, + relay_fee: u64, +) -> Result { + use ark_std::rand::SeedableRng; + let merkle_proof = tree.proof(index as u64).map_err(|e| anyhow!("{e}"))?; + let binding = mirror_core::action_binding( + SELECTOR_TRANSFER, + &[0u8; 32], + &beneficiary.to_bytes(), + relay_fee, + 0, + &[], + ); + let witness = Witness { + note: notes[index], + merkle_proof: &merkle_proof, + root: tree.root().map_err(|e| anyhow!("{e}"))?, + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([index as u8 + 1; 32]); + prove(keys, &witness, &mut rng).map_err(|e| anyhow!("{e}")) +} + /// The full lifecycle, on a live cluster, with every signature recorded. pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Result<()> { let program_id: Pubkey = program @@ -490,35 +526,76 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re soak.accounting = Some(accounting); println!("\nnegative path:"); - // A replayed proof: the nullifier record already exists. + + // One more note, deposited only now. Two of the three negatives below need + // a *live* note: a rejection against an already-spent one proves the replay + // guard fired first and says nothing about the check under test. It is + // deposited after settlement so the accounting above still describes a + // vault that emptied to its floor rather than one holding a spare. + let (tree, notes) = soak.deposit(1, ledger)?; + let spare = notes.len() - 1; + + // A replayed proof: honest in every respect, against a note already spent. { - use ark_std::rand::SeedableRng; let beneficiary = Keypair::new().pubkey(); - let merkle_proof = tree.proof(first_fresh as u64).map_err(|e| anyhow!("{e}"))?; - let binding = mirror_core::action_binding( - SELECTOR_TRANSFER, - &[0u8; 32], - &beneficiary.to_bytes(), + let proof = proof_for_note(&keys, &tree, ¬es, first_fresh, &beneficiary, RELAY_FEE)?; + let ix = soak.spend_ix_for( + proof.public_inputs[1], + &proof, + &beneficiary, + &payer.pubkey(), RELAY_FEE, - 0, - &[], ); - let witness = Witness { - note: notes[first_fresh], - merkle_proof: &merkle_proof, - root: tree.root().map_err(|e| anyhow!("{e}"))?, - action_binding: binding, - }; - let mut rng = ark_std::rand::rngs::StdRng::from_seed([first_fresh as u8 + 1; 32]); - let proof = prove(&keys, &witness, &mut rng).map_err(|e| anyhow!("{e}"))?; + soak.expect_rejection( + "replayed nullifier", + "a nullifier is spent once, ever -- not once per epoch", + ix, + &[&payer], + )?; + } + + // A relay redirecting a payout. The proof is genuine and the note is live; + // the relay simply names a different beneficiary in the instruction. The + // binding is recomputed on-chain from what the instruction says, so it no + // longer matches the one inside the proof and the pairing fails. + { + let member_chose = Keypair::new().pubkey(); + let relay_prefers = Keypair::new().pubkey(); + let proof = proof_for_note(&keys, &tree, ¬es, spare, &member_chose, RELAY_FEE)?; let ix = soak.spend_ix_for( proof.public_inputs[1], &proof, - &beneficiary, + &relay_prefers, &payer.pubkey(), RELAY_FEE, ); - soak.expect_rejection("replayed nullifier", ix, &[&payer])?; + soak.expect_rejection( + "redirected beneficiary", + "a relay cannot send a member's payout somewhere the member did not choose", + ix, + &[&payer], + )?; + } + + // The other half of the same claim: a relay re-pricing its own fee. Same + // live note, same genuine proof, and the only change is the fee the + // instruction declares. + { + let beneficiary = Keypair::new().pubkey(); + let proof = proof_for_note(&keys, &tree, ¬es, spare, &beneficiary, RELAY_FEE)?; + let ix = soak.spend_ix_for( + proof.public_inputs[1], + &proof, + &beneficiary, + &payer.pubkey(), + RELAY_FEE * 10, + ); + soak.expect_rejection( + "inflated relay fee", + "a relay cannot re-price the work after the member authorised it", + ix, + &[&payer], + )?; } write_proof(&soak, url, out)?; @@ -601,10 +678,17 @@ fn write_proof(soak: &Soak, url: &str, out: &std::path::Path) -> Result<()> { "A negative case is only evidence if the program's own error code is what \ rejected it. A bare runtime failure proves nothing about this program.\n\n", ); - md.push_str("| case | error code |\n|---|---|\n"); - for (name, code) in &soak.negatives { - md.push_str(&format!("| {name} | `{code}` |\n")); + md.push_str("| case | error code | what the rejection establishes |\n|---|---|---|\n"); + for (name, code, proves) in &soak.negatives { + md.push_str(&format!("| {name} | `{code}` | {proves} |\n")); } + md.push_str( + "\nThe last two attack a note that is still live, deposited after settlement \ + precisely so that they would have to. Against an already-spent note the \ + replay guard fires first and the rejection would say nothing about the \ + check under test — which is how a negative case comes to pass for the \ + wrong reason.\n", + ); md.push_str("\n## Scope\n\n"); md.push_str( diff --git a/data/soak-notes.json b/data/soak-notes.json index d05e0229..d685c825 100644 --- a/data/soak-notes.json +++ b/data/soak-notes.json @@ -1 +1 @@ -[[1785041911109669,14135704486965],[1785041911109676,14135704486978],[1785041911109683,14135704486991],[1785041911109690,14135704487004]] \ No newline at end of file +[[1785042846112474,14135711891230],[1785042846112481,14135711891243],[1785042846112488,14135711891256],[1785042846112495,14135711891269],[1785042867112537,14135712057529]] \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 41ba823b..9446a2c5 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -78,8 +78,8 @@ credited to it, and pool creation refuses a nonzero entry fee, so there is no second category of lamports anywhere that could be mistaken for backing for an unspent note. -`docs/PROOF.md` carries the devnet numbers: four notes settled, 80,000,012 -lamports owed and 80,000,012 paid, and a vault that came to rest on its +`docs/PROOF.md` carries the devnet numbers: four notes settled, 80,000,028 +lamports owed and 80,000,028 paid, and a vault that came to rest on its rent-exempt floor with a remainder of zero. The soak asserts that rather than printing it, so a run that disagreed would fail instead of publishing. diff --git a/docs/PROOF.md b/docs/PROOF.md index 30799af0..a6877d92 100644 --- a/docs/PROOF.md +++ b/docs/PROOF.md @@ -4,23 +4,24 @@ Every line here is a transaction that landed. Nothing is simulated: the proof is - cluster: `devnet` - program: `8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa` -- pool: `ABb58741sCZV6nWtwfK1dEqonLBp2dGDiAe85jBnWa22` -- vault: `5vWyEoKQjoYMUnyRe1yzSnGGUBfhs654ssxiFadwxB5t` +- pool: `J68VyjjKQTvg7tsZJHoCEQ5nMYGnUVs4esA7EF1nP8Ui` +- vault: `54PT56YwQWJA6UAYNsnqKPJq4SrCW2gnF7HVvU8QDk7a` ## Flows | step | signature | note | |---|---|---| -| init_pool | [`a1Um5GdackChmKVUNUgneYCEFiqQ4hjcqVQFhb9tTeAiYNQBRKCbgCJf8TAYXWP877b95ZDAxcebJWrbrnSVRFm`](https://explorer.solana.com/tx/a1Um5GdackChmKVUNUgneYCEFiqQ4hjcqVQFhb9tTeAiYNQBRKCbgCJf8TAYXWP877b95ZDAxcebJWrbrnSVRFm?cluster=devnet) | denomination 20000003, k_floor 4 | -| deposit | [`5ZJhBMLfajJBxfxeHgArQ25uHx6XZDp8PH44W4wuE4g15yexTmiC6oJJXfaLBHu9MLXRD3ggwEpB6FVpTMWvV8Py`](https://explorer.solana.com/tx/5ZJhBMLfajJBxfxeHgArQ25uHx6XZDp8PH44W4wuE4g15yexTmiC6oJJXfaLBHu9MLXRD3ggwEpB6FVpTMWvV8Py?cluster=devnet) | note 1 | -| deposit | [`3cXFiZSM2Z3onk31w8Q7Q21d56ZqrNACEQh4VdzLg2ub8aXURRVuZLL9TKqcnFWAtiftYRoGn9xxERx3RF21zVmX`](https://explorer.solana.com/tx/3cXFiZSM2Z3onk31w8Q7Q21d56ZqrNACEQh4VdzLg2ub8aXURRVuZLL9TKqcnFWAtiftYRoGn9xxERx3RF21zVmX?cluster=devnet) | note 2 | -| deposit | [`4HSKcvxtBnxSeECCEc2UPCfc4xoUcbiMminMET73A5mAwqPtjR5u2prLgRaxPMcWS18EZB5pbktKhAZdGLXB45LG`](https://explorer.solana.com/tx/4HSKcvxtBnxSeECCEc2UPCfc4xoUcbiMminMET73A5mAwqPtjR5u2prLgRaxPMcWS18EZB5pbktKhAZdGLXB45LG?cluster=devnet) | note 3 | -| deposit | [`4vXba4TPxyGJeZosLnaDddJom7yerWCVAGoMHQsT1RMwCS7NQ34rEbX1TqA6DUYUV3Pugj7SDY34DN8MaruHMVpP`](https://explorer.solana.com/tx/4vXba4TPxyGJeZosLnaDddJom7yerWCVAGoMHQsT1RMwCS7NQ34rEbX1TqA6DUYUV3Pugj7SDY34DN8MaruHMVpP?cluster=devnet) | note 4 | -| submit_spend | [`2AxhzG9wa3nVsuxNJf7WmhVYHNQw1QimrFvSdBKgz3ZyyTJbr9D4iDtzVq8Ap7f4vqVrKCcsWLabVUpEhiPmCu6R`](https://explorer.solana.com/tx/2AxhzG9wa3nVsuxNJf7WmhVYHNQw1QimrFvSdBKgz3ZyyTJbr9D4iDtzVq8Ap7f4vqVrKCcsWLabVUpEhiPmCu6R?cluster=devnet) | note 0, relay-signed | -| submit_spend | [`4n5zRhNC3GpmDAxoauNMsb9xm9FWDgqMMFGSKuB8176xBrAVPCUSx54uBZphVdHbUu3LZeNgXCgQCaUDorynKU73`](https://explorer.solana.com/tx/4n5zRhNC3GpmDAxoauNMsb9xm9FWDgqMMFGSKuB8176xBrAVPCUSx54uBZphVdHbUu3LZeNgXCgQCaUDorynKU73?cluster=devnet) | note 1, relay-signed | -| submit_spend | [`5TTZhv3kJ1EQpVjdh3nUZt5rgPHSXzfqWfyeGx97vj8XM4BJz1orxc6pYqiSirqGUWUbVkX6zvmiN65RcGcRp71v`](https://explorer.solana.com/tx/5TTZhv3kJ1EQpVjdh3nUZt5rgPHSXzfqWfyeGx97vj8XM4BJz1orxc6pYqiSirqGUWUbVkX6zvmiN65RcGcRp71v?cluster=devnet) | note 2, relay-signed | -| submit_spend | [`eiczViayoZXPJ5VbEoFSQT6Pu9udpZByo532RA7YTWfKSqoL7GqcuhiCxZmeyekSeyyRW8GKstzNCFLdajW4M7G`](https://explorer.solana.com/tx/eiczViayoZXPJ5VbEoFSQT6Pu9udpZByo532RA7YTWfKSqoL7GqcuhiCxZmeyekSeyyRW8GKstzNCFLdajW4M7G?cluster=devnet) | note 3, relay-signed | -| settle_epoch | [`44qJhqRE1rxkZqqbgnKoy1vZBoqa2P3dmH6zuwbQy4RgCp72XgUkWqqjiaNR9iUXp2gZndtcxMuLWsH4so6d2TiF`](https://explorer.solana.com/tx/44qJhqRE1rxkZqqbgnKoy1vZBoqa2P3dmH6zuwbQy4RgCp72XgUkWqqjiaNR9iUXp2gZndtcxMuLWsH4so6d2TiF?cluster=devnet) | 4 spends | +| init_pool | [`2i2dSth52MNMboBjL7s28MaArrh9Vmj2z7Rm21CYpig79czESepPUyqkDPiyvM8UvV2NP587kyZUqxhZYB2CJrdB`](https://explorer.solana.com/tx/2i2dSth52MNMboBjL7s28MaArrh9Vmj2z7Rm21CYpig79czESepPUyqkDPiyvM8UvV2NP587kyZUqxhZYB2CJrdB?cluster=devnet) | denomination 20000007, k_floor 4 | +| deposit | [`3uFCoMKsmeyWFbvRFE2qiwWm3VRTZmdMW4tnsSxLMCMuXfhzrt7irdjE9S1R86HmzC3vc7j1BKpNbj893CrzW8RX`](https://explorer.solana.com/tx/3uFCoMKsmeyWFbvRFE2qiwWm3VRTZmdMW4tnsSxLMCMuXfhzrt7irdjE9S1R86HmzC3vc7j1BKpNbj893CrzW8RX?cluster=devnet) | note 1 | +| deposit | [`4dtLcdt9CdCdoimcgNLouG8Kh72oMfy1jrB2p5e8muAUXc4asGk2SsSgsERhzUjXNHZSmr2xMWfWXcnJvX9sav2H`](https://explorer.solana.com/tx/4dtLcdt9CdCdoimcgNLouG8Kh72oMfy1jrB2p5e8muAUXc4asGk2SsSgsERhzUjXNHZSmr2xMWfWXcnJvX9sav2H?cluster=devnet) | note 2 | +| deposit | [`4nRS8qTSW9h58kimVCX8D7YWHHw4uicBnCfZcPqqLdcHcUCxnhGbX7GTjRKuy9RLSeHbrALGp7Gu3RAgfRMaJkSX`](https://explorer.solana.com/tx/4nRS8qTSW9h58kimVCX8D7YWHHw4uicBnCfZcPqqLdcHcUCxnhGbX7GTjRKuy9RLSeHbrALGp7Gu3RAgfRMaJkSX?cluster=devnet) | note 3 | +| deposit | [`4F5nMtQTdEHiBb2yDcZhwMtscsdFbxEZXxJvAt3ZAxYvTLJsC2bgQbiiw8pCsMYizuFUivHgQcz3JXG75x5efYEs`](https://explorer.solana.com/tx/4F5nMtQTdEHiBb2yDcZhwMtscsdFbxEZXxJvAt3ZAxYvTLJsC2bgQbiiw8pCsMYizuFUivHgQcz3JXG75x5efYEs?cluster=devnet) | note 4 | +| submit_spend | [`48RWESn6bf5ipZi9uYhUo31XJw4RcqZyxT3vk3U8g2UZ8bceZihUmUJrZCJTt9e18Mi2ymfQW8j26Vvoo2fKgYZu`](https://explorer.solana.com/tx/48RWESn6bf5ipZi9uYhUo31XJw4RcqZyxT3vk3U8g2UZ8bceZihUmUJrZCJTt9e18Mi2ymfQW8j26Vvoo2fKgYZu?cluster=devnet) | note 0, relay-signed | +| submit_spend | [`4QFM6p4cniHTmATLJPuMR89DyYohFM5N3wrXYGJJpiAQ3xzNLgz8hCD5pN5BEoyysRYDc6FcQD2xbRZggrw2Nsmi`](https://explorer.solana.com/tx/4QFM6p4cniHTmATLJPuMR89DyYohFM5N3wrXYGJJpiAQ3xzNLgz8hCD5pN5BEoyysRYDc6FcQD2xbRZggrw2Nsmi?cluster=devnet) | note 1, relay-signed | +| submit_spend | [`FHHgBrh8ytutD58XtSTGUKBJpgqq5rV9krBgq9VheaBJQEpQcucZkU85mHX9KnuvEiazDA753Se2azDSPb4RmtZ`](https://explorer.solana.com/tx/FHHgBrh8ytutD58XtSTGUKBJpgqq5rV9krBgq9VheaBJQEpQcucZkU85mHX9KnuvEiazDA753Se2azDSPb4RmtZ?cluster=devnet) | note 2, relay-signed | +| submit_spend | [`4ZFy3xaCokZoqWYvEw3iwRYQQr6obpMqdKjUvtUqhaPZqRW3RFSzX3Eva7QdvJLpnpMRXhvw9cEB26NY9Tbh8o6P`](https://explorer.solana.com/tx/4ZFy3xaCokZoqWYvEw3iwRYQQr6obpMqdKjUvtUqhaPZqRW3RFSzX3Eva7QdvJLpnpMRXhvw9cEB26NY9Tbh8o6P?cluster=devnet) | note 3, relay-signed | +| settle_epoch | [`67SdKHQ2fHcJ8nommceaaJnnFaCL5mtKwtuyy2uTHB2Lz8aauyDtGDTQ8SJFC28LMM33jZQHjb3cm3x9BXRiPM8W`](https://explorer.solana.com/tx/67SdKHQ2fHcJ8nommceaaJnnFaCL5mtKwtuyy2uTHB2Lz8aauyDtGDTQ8SJFC28LMM33jZQHjb3cm3x9BXRiPM8W?cluster=devnet) | 4 spends | +| deposit | [`4fcgHaFewWfoy2QMqpGTChxuhfFg8fTeWts6sW45rR1ZhJMk9fR4X6AQnZXBxiZW3Si55VNpSzuxh8FtL1p8ENtt`](https://explorer.solana.com/tx/4fcgHaFewWfoy2QMqpGTChxuhfFg8fTeWts6sW45rR1ZhJMk9fR4X6AQnZXBxiZW3Si55VNpSzuxh8FtL1p8ENtt?cluster=devnet) | note 5 | ## Vault accounting @@ -28,14 +29,14 @@ The accounting invariant is a statement about the vault's lamports, so here are | quantity | lamports | |---|---| -| denomination | 20000003 | +| denomination | 20000007 | | notes settled | 4 | | relay fee (taken out of the denomination, not added) | 200000 | -| vault before settlement | 80890892 | +| vault before settlement | 80890908 | | vault after settlement | 890880 | | rent-exempt minimum, 0 bytes | 890880 | -| **paid out** | **80000012** | -| **owed** (denomination × notes) | **80000012** | +| **paid out** | **80000028** | +| **owed** (denomination × notes) | **80000028** | Paid out equals owed, and the vault came to rest on its floor with a remainder of 0 lamports. The soak asserts both and fails the run otherwise, so this table cannot record a discrepancy and still exit successfully. @@ -43,9 +44,13 @@ Paid out equals owed, and the vault came to rest on its floor with a remainder o A negative case is only evidence if the program's own error code is what rejected it. A bare runtime failure proves nothing about this program. -| case | error code | -|---|---| -| replayed nullifier | `0xf` | +| case | error code | what the rejection establishes | +|---|---|---| +| replayed nullifier | `0xf` | a nullifier is spent once, ever -- not once per epoch | +| redirected beneficiary | `0x12` | a relay cannot send a member's payout somewhere the member did not choose | +| inflated relay fee | `0x12` | a relay cannot re-price the work after the member authorised it | + +The last two attack a note that is still live, deposited after settlement precisely so that they would have to. Against an already-spent note the replay guard fires first and the rejection would say nothing about the check under test — which is how a negative case comes to pass for the wrong reason. ## Scope diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index b3b60dd9..7681dbe6 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -159,8 +159,25 @@ evidence of behaviour and not a substitute for an audit. ## Deliberate non-goals -**Hiding funds.** The brief asks for behavioural deniability, not a mixer, and a -value-mixing layer is not here. +**Hiding funds** — with a precision this deserves, because the protocol plainly +touches value and a flat "not a mixer" would be too convenient. + +What is here: a fixed-denomination escrow whose purpose is to make members +interchangeable and to fund the actions they authorise. Selector one invokes an +arbitrary program, which is the point of the design; selector zero is a plain +transfer, kept because expressing "pay this account" should not require a target +program. That selector does mean a member can deposit a denomination and have it +paid to an address of their choosing, unlinked to the deposit. Calling that +anything other than what it is would be dishonest. + +What is *not* here, and is what the word "mixer" usually means: confidential +amounts, a value-shielding layer, or any attempt to obscure how much moved. +Denominations are pool constants and public. Nothing hides quantity. + +And what the design is *for* is the behavioural case. The measurement, the +crowd rule, the shared settlement timestamp and the CPI dispatch all exist to +make a stake or a vote unattributable. A submission that wanted a value mixer +would not need any of them. **Defeating an adversary with off-chain data.** Someone who knows a member deposited — because they watched them do it, or because the member told them — From d0f2b6c6fc2d808cf90fa63beb10c4700ae6d09a Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 00:59:12 -0300 Subject: [PATCH 38/83] docs: state what the measurement does not support, now that it supports something The log's closing section still disclaimed Run 3's refused number. Run 4 produced a headline, and a headline needs a sharper boundary than a refusal did: clearing an informativeness gate is not the same as having measured how private a pool is. Three specific non-conclusions, each one a claim the number invites and does not carry: no cross-pool comparison, because ro is built to be comparable and only one pool has been measured; no extrapolation to larger k, which the heavy tail makes worse rather than better; and nothing about any individual member. --- docs/MEASUREMENT_LOG.md | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index 01039155..bde3ccb5 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -239,7 +239,27 @@ numbers above are recomputable from either without RPC access. ## What we do not conclude -Nothing about how private Privacy Cash is. ρ = 0.1219 is a point estimate inside -a bracket that spans an order of magnitude, from a sample whose class -distribution is two-thirds unobserved. The number is published because the method -is published, not because it settles anything. +**Nothing about how private that pool is, and the headline does not become that +claim by clearing a gate.** ρ = 0.1032 is a point estimate inside a bracket +spanning 0.032 to 0.132, drawn from one frame of one pool at one moment, and its +class distribution is demonstrably heavy-tailed and two-thirds unobserved. It is +published because the method is published, not because it settles anything. + +Three things it specifically does not support: + +- **A comparison between pools.** ρ is designed to be comparable across pools — + that is why it is the headline — but only one pool has been measured. The + property is asserted here and not yet exercised. +- **An extrapolation to larger `k`.** Effective-k measured at small `k` + understates the steady-state loss, and the heavy tail found in Run 4 is a + reason to expect that gap to widen rather than close. +- **A statement about any individual member.** The metric is a property of a + distribution. Nothing here identifies anyone, and the worst-case class of size + one is a feature of heavy-tailed provenance in general, not a finding about a + person. + +What the four runs do support is narrower and, we think, more useful: that this +channel is measurable from live Solana data with a published method, that the +measurement is expensive and the tail does not close, and that a pipeline built +to refuse is one that refuses — three times, correctly, before it produced a +number. From 74873f9ebd64b173c53705103bae46fc825dd7b8 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 01:37:07 -0300 Subject: [PATCH 39/83] docs: pre-register Run 5, a control population that is not seeking privacy Run 4's rho = 0.1032 has no scale. A reader cannot tell whether that is concentrated or ordinary, because nothing has been measured beside it, and rho was chosen as the headline specifically because it is comparable across populations -- a property asserted four runs running and never exercised. The comparison that supplies the scale is not a second privacy pool. It is a population that is not seeking privacy at all, put through the identical pipeline: Marinade liquid staking depositors. If ordinary users show the same concentration then 0.10 describes Solana's funding graph rather than the pool, which would be the more interesting outcome and is recorded here as the prediction that would undercut our own framing. A feasibility probe established the frame is constructible at roughly a third of the privacy pool's yield per fetch. It was not run through analyze; nothing about the outcome is known as this is written. --- docs/MEASUREMENT_LOG.md | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index bde3ccb5..99e8bbd4 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -237,6 +237,55 @@ rather than a caveat.** artifact stays committed beside the Run 4 one; both are in `data/`, and the numbers above are recomputable from either without RPC access. +## Run 5 — pre-registered before collection, 2026-07-26 + +Committed before the collection started, like Run 4. + +**Why a second population, and why this one.** Run 4 produced ρ = 0.1032 for a +privacy pool's depositors. On its own that number has no scale: a reader cannot +tell whether 0.10 is concentrated or ordinary, because there is nothing to +compare it against. ρ was chosen as the headline precisely because it is +independent of `k` and therefore comparable across populations — and so far that +property has been asserted and never exercised. + +The comparison that gives the number a scale is not a second privacy pool. It is +a population that **is not seeking privacy at all**, measured by the identical +pipeline. If ordinary users show the same provenance concentration, then 0.10 +describes Solana rather than the pool. If the privacy pool is more concentrated, +that is a finding about who privacy tools attract. Either answer is worth more +than a second point on the same curve. + +**Frame.** Depositors of Marinade liquid staking, +`MarBmsSgKXdrN1egZf5sqe1TMai9K1rChYNDJgjq7aD`, enumerated by `mirror seeds` from +the program's own transactions, member-weighted and strided across signature +pages exactly as the Privacy Cash frame was. Target 100 depositors. + +This is a **control, not a privacy pool**, and it is labelled as one everywhere. +Staking SOL is an ordinary, publicly-attributable act; nobody does it for +deniability. + +**What a feasibility probe already established, and what it did not.** A probe of +178 transactions returned 5 distinct depositors — a yield near 3%, against about +12% for the privacy pool, because most Marinade program traffic is unstaking and +bot activity rather than deposits. So the frame is constructible but costs +roughly four times as many fetches per member. That is a fact about the budget. +The probe was not run through `analyze` and nothing about the outcome is known +at the time of writing. + +**Prediction, recorded in advance.** Genuinely uncertain, which is the reason to +write it down. The expectation is that the control is **less** concentrated — +lower ρ — because a privacy pool is plausibly reached through a narrower set of +funding routes. A control that came back at or above 0.10 would say the +concentration is a property of Solana's funding graph rather than of the pool, +and that would be the more interesting result of the two. + +The under-sampling flag is expected to fire again. Nothing about the tail found +in Run 4 suggests a different population would have a lighter one. + +**Stopping rule.** One run at this frame size. Whatever it returns is reported, +including a result that undercuts the argument for measuring privacy pools at +all, and including a refusal. + ## What we do not conclude **Nothing about how private that pool is, and the headline does not become that From 36f6f3b2040530ce54db63ffedf3f81d0ee87dc6 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 01:49:08 -0300 Subject: [PATCH 40/83] measure: quantify the sampling error in rho, and compare two populations honestly Four runs reported rho as a point estimate with an unresolved bracket beside it. Those are two different uncertainties and only one was quantified: the bracket bounds what the unresolved members could have been, and nothing at all covered the fact that these depositors are a draw from a much larger population. That gap becomes disqualifying the moment two populations are compared. Two samples of the same underlying shape differ by something, and reporting that something as a finding is exactly the move this project criticises elsewhere. Adds a bootstrap over members: resample the resolved set with replacement, recompute rho, take a percentile interval. A `compare` command does the same for the difference between two populations and refuses to rank them when the interval contains zero. The generator is a splitmix64 written here rather than a dependency, because analyze is a pure offline pass whose numbers must be recomputable byte for byte by a third party -- including the interval. Seed and replicate count are published constants. Two things it deliberately does not fix, both stated in the module and in the analyze output: plug-in entropy is biased low at small n, so rho is biased HIGH, and a bootstrap resamples the same biased estimator. The bias falls in the same direction on every population, which is what lets a comparison survive a bias that neither individual number does. Checked against the two samples already committed: Runs 3 and 4 are the same pool at two budgets, and the tool correctly reports them as not separated. 191 tests. --- crates/mirror-cli/src/main.rs | 148 +++++++++ crates/mirror-provenance/src/bootstrap.rs | 355 ++++++++++++++++++++++ crates/mirror-provenance/src/lib.rs | 2 + data/seeds-marinade.txt | 100 ++++++ 4 files changed, 605 insertions(+) create mode 100644 crates/mirror-provenance/src/bootstrap.rs create mode 100644 data/seeds-marinade.txt diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 8cd78633..3f76da1c 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -124,6 +124,30 @@ enum Command { #[arg(long, default_value = "sample.json")] sample: PathBuf, }, + /// Compares the loss factor of two populations measured the same way. + /// + /// `ρ` is the headline because it is independent of `k` and therefore + /// comparable across pools of different sizes. This is the command that + /// exercises that property rather than asserting it. + /// + /// The comparison is a bootstrap over the difference, not a subtraction of + /// two point estimates. Two samples drawn from the same underlying shape + /// will differ by *something*, and reporting that something as a finding is + /// the error this command exists to prevent: if the interval contains zero, + /// it says so and declines to rank them. + Compare { + /// The population being examined. + #[arg(long)] + sample: PathBuf, + /// The population it is measured against. + #[arg(long)] + against: PathBuf, + /// A name for each, used only in the output. + #[arg(long, default_value = "sample")] + label: String, + #[arg(long, default_value = "baseline")] + against_label: String, + }, /// Runs the whole lifecycle against a live cluster and prints every /// signature, so the result is checkable rather than asserted. Soak { @@ -159,6 +183,37 @@ fn vk_digest(vk: &mirror_circuit::SolanaVerifyingKey) -> String { hex::encode(hasher.finalize()) } +/// Loads a committed sample and returns one class label per resolved member. +/// +/// Applies the same failure gate `analyze` does. A comparison drawn from a run +/// whose unresolved bucket is substantially our own infrastructure would be +/// comparing endpoints rather than populations, and it would do so invisibly — +/// the difference between two pools and the difference between two collection +/// runs look identical in the output. +fn resolved_labels(path: &std::path::Path) -> Result> { + let text = + std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?; + let sample = mirror_provenance::Sample::from_json(&text)?; + let (results, census) = mirror_provenance::classify_sample( + &sample, + &mirror_provenance::AnchorSet::default(), + &mirror_provenance::Thresholds::default(), + sample.manifest.collected_at, + ); + if !census.may_publish() { + anyhow::bail!( + "{}: the RPC failure rate is {:.2}%, above the 1% limit. This sample yields no \ + headline on its own and cannot be one side of a comparison.", + path.display(), + census.failure_rate() * 100.0 + ); + } + Ok(results + .iter() + .filter_map(|(_, o)| o.label().map(|s| s.to_string())) + .collect()) +} + fn main() -> Result<()> { match Cli::parse().command { Command::Setup { seed, out } => { @@ -451,6 +506,29 @@ fn main() -> Result<()> { println!(" t={t:<4} {:.4}", share); } + // Sampling error, which the bracket below does not cover. + // These depositors are a draw from a larger population, and + // without an interval over that draw a reader cannot tell a + // real difference between two pools from a lucky sample. + if let Some(i) = mirror_provenance::loss_factor_interval( + &labels, + mirror_provenance::bootstrap::DEFAULT_REPLICATES, + mirror_provenance::bootstrap::DEFAULT_SEED, + ) { + println!(); + println!( + "rho sampling interval {:.4} .. {:.4} (95%, {} bootstrap replicates \ + over the members drawn)", + i.lo, i.hi, i.replicates + ); + println!( + " This is the spread of the estimator, not its distance from the \ + truth. Plug-in\n entropy is biased low at small n, so rho is biased \ + HIGH: the real loss factor is\n plausibly below this interval, and \ + equally so for any population measured this way." + ); + } + // The bracket. A point estimate alone would not say whether // the number is driven by what was measured or by what was // not. @@ -499,6 +577,76 @@ fn main() -> Result<()> { } Ok(()) } + Command::Compare { + sample, + against, + label, + against_label, + } => { + let a = resolved_labels(&sample)?; + let b = resolved_labels(&against)?; + + let reps = mirror_provenance::bootstrap::DEFAULT_REPLICATES; + let seed = mirror_provenance::bootstrap::DEFAULT_SEED; + + let ia = mirror_provenance::loss_factor_interval(&a, reps, seed) + .ok_or_else(|| anyhow::anyhow!("{label}: no member resolved to a class"))?; + let ib = mirror_provenance::loss_factor_interval(&b, reps, seed) + .ok_or_else(|| anyhow::anyhow!("{against_label}: no member resolved to a class"))?; + + let width = label.len().max(against_label.len()).max(10); + println!("{:7} {:.4} {:.4} .. {:.4}", + a.len(), + ia.point, + ia.lo, + ia.hi + ); + println!( + "{against_label:7} {:.4} {:.4} .. {:.4}", + b.len(), + ib.point, + ib.lo, + ib.hi + ); + + let d = mirror_provenance::difference_interval(&a, &b, reps, seed) + .ok_or_else(|| anyhow::anyhow!("nothing to compare"))?; + println!(); + println!( + "difference ({label} − {against_label}): {:+.4} 95% {:+.4} .. {:+.4}", + d.point, d.lo, d.hi + ); + println!(); + + if d.excludes_zero() { + let (more, less) = if d.point > 0.0 { + (&label, &against_label) + } else { + (&against_label, &label) + }; + println!( + "SEPARATED. The interval on the difference excludes zero, so at these sample\n\ + sizes {more} is the more provenance-concentrated population of the two —\n\ + a member's funding class narrows the guess further there than in {less}." + ); + } else { + println!( + "NOT SEPARATED. The interval on the difference contains zero, so these two\n\ + populations are indistinguishable at these sample sizes. The point estimates\n\ + differ, and that difference is not evidence: quoting it as one would be\n\ + reporting the draw." + ); + } + println!(); + println!( + "Both estimates share the same downward bias in entropy, so both rho values are\n\ + biased high by roughly the same amount. That is why the comparison survives a\n\ + bias that neither individual number does." + ); + Ok(()) + } Command::Soak { program, url, diff --git a/crates/mirror-provenance/src/bootstrap.rs b/crates/mirror-provenance/src/bootstrap.rs new file mode 100644 index 00000000..105a8e64 --- /dev/null +++ b/crates/mirror-provenance/src/bootstrap.rs @@ -0,0 +1,355 @@ +//! Sampling error for `ρ`, by bootstrap over members. +//! +//! Every run so far has reported `ρ` as a point estimate with an *unresolved +//! bracket* beside it. Those are two different uncertainties, and only one of +//! them was quantified. +//! +//! * The **bracket** answers: what if the members we could not resolve had +//! landed in one class, or in classes of their own? It is a bound, not a +//! distribution, and it is computed exactly. +//! * The **bootstrap here** answers: we drew *these* depositors out of a much +//! larger population — how much of `ρ` is the draw? Resampling the resolved +//! members with replacement and recomputing `ρ` each time gives a percentile +//! interval for that. +//! +//! Both are needed and neither substitutes for the other. Comparing two +//! populations without the second one is eyeballing two numbers and calling the +//! larger one larger, which is precisely the move this project criticises +//! elsewhere. +//! +//! ## What this does not fix +//! +//! **Plug-in entropy is biased downward at small `n`.** Estimating `H(C)` by +//! counting is a maximum-likelihood estimate, and it systematically understates +//! entropy when classes are many and members are few — which is exactly the +//! regime every run here operates in. A bootstrap resamples the same estimator, +//! so its interval is centred on the *biased* value: it measures the spread of +//! the estimate, not its distance from the truth. Since `ρ = 2^{−H(C)}`, +//! understated entropy means **overstated ρ**, so the true loss factor is +//! plausibly smaller than any number reported here. +//! +//! Correcting that needs a different estimator — Miller–Madow, or one of the +//! coverage-adjusted families — and this crate does not implement one. The +//! consequence is stated wherever a `ρ` appears rather than left for a reader to +//! infer, and it applies **in the same direction to every population measured**, +//! which is what keeps a comparison between two of them meaningful even while +//! each is individually biased. + +use crate::metrics::Anonymity; +use std::collections::BTreeMap; + +/// A deterministic splitmix64. +/// +/// Deliberately not an external RNG. `analyze` is a pure, offline, reproducible +/// pass: anyone holding a committed sample must be able to recompute every +/// published figure and get the same bytes, and that includes the interval. A +/// seeded generator implemented here is reproducible across platforms and +/// dependency versions in a way that "whatever `rand` does this year" is not. +struct SplitMix64 { + state: u64, +} + +impl SplitMix64 { + fn new(seed: u64) -> Self { + SplitMix64 { state: seed } + } + + fn next_u64(&mut self) -> u64 { + self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15); + let mut z = self.state; + z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9); + z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB); + z ^ (z >> 31) + } + + /// Uniform on `0..n`, rejecting the biased tail rather than taking a modulus + /// over it. + fn below(&mut self, n: u64) -> u64 { + debug_assert!(n > 0); + let zone = u64::MAX - (u64::MAX % n); + loop { + let v = self.next_u64(); + if v < zone { + return v % n; + } + } + } +} + +/// A percentile interval, and the point estimate it surrounds. +#[derive(Debug, Clone, PartialEq)] +pub struct Interval { + /// The estimate computed from the sample as drawn. + pub point: f64, + pub lo: f64, + pub hi: f64, + pub replicates: usize, +} + +impl Interval { + /// Whether the interval excludes zero, for a difference between two + /// populations. + /// + /// Meaningless for a single `ρ`, which is positive by construction. + pub fn excludes_zero(&self) -> bool { + self.lo > 0.0 || self.hi < 0.0 + } + + pub fn width(&self) -> f64 { + self.hi - self.lo + } +} + +/// Default replicate count. Enough that the 2.5th and 97.5th percentiles are +/// stable to about three decimals, and cheap: each replicate is O(n). +pub const DEFAULT_REPLICATES: usize = 10_000; + +/// The fixed seed every published interval is computed at. +/// +/// Published rather than arbitrary, so a reader recomputing the number gets our +/// number and not merely a similar one. +pub const DEFAULT_SEED: u64 = 0x6D69_7272_6F72_0501; + +fn class_indices(labels: &[L]) -> Vec { + let mut index: BTreeMap = BTreeMap::new(); + let mut out = Vec::with_capacity(labels.len()); + for l in labels { + let next = index.len(); + let id = *index.entry(l.clone()).or_insert(next); + out.push(id); + } + out +} + +/// One bootstrap replicate: resample `n` members with replacement and recompute +/// the loss factor. +fn replicate(members: &[usize], classes: usize, rng: &mut SplitMix64) -> Option { + let n = members.len(); + let mut counts = vec![0u64; classes]; + for _ in 0..n { + let pick = members[rng.below(n as u64) as usize]; + counts[pick] += 1; + } + // A class that no resampled member landed in is absent from this replicate, + // not a class of size zero. `Anonymity` rejects a zero, correctly. + let sizes: Vec = counts.into_iter().filter(|&c| c > 0).collect(); + Anonymity::from_class_sizes(&sizes).map(|a| a.loss_factor) +} + +fn percentile(sorted: &[f64], q: f64) -> f64 { + if sorted.is_empty() { + return f64::NAN; + } + let idx = (q * (sorted.len() - 1) as f64).round() as usize; + sorted[idx.min(sorted.len() - 1)] +} + +/// A 95% percentile interval for `ρ` over the draw of members. +/// +/// `labels` is one class label per **resolved** member. Unresolved members are +/// not represented: they are the bracket's business, not this one's. +pub fn loss_factor_interval( + labels: &[L], + replicates: usize, + seed: u64, +) -> Option { + let point = Anonymity::from_labels(labels)?.loss_factor; + let members = class_indices(labels); + let classes = members.iter().copied().max().map(|m| m + 1)?; + + let mut rng = SplitMix64::new(seed); + let mut draws: Vec = Vec::with_capacity(replicates); + for _ in 0..replicates { + if let Some(r) = replicate(&members, classes, &mut rng) { + draws.push(r); + } + } + if draws.is_empty() { + return None; + } + draws.sort_by(|a, b| a.partial_cmp(b).expect("no NaN in a loss factor")); + + Some(Interval { + point, + lo: percentile(&draws, 0.025), + hi: percentile(&draws, 0.975), + replicates: draws.len(), + }) +} + +/// A 95% percentile interval for `ρ(a) − ρ(b)`. +/// +/// Each population is resampled independently within a replicate, which is the +/// right structure here: the two frames are drawn from different populations and +/// share no members. +/// +/// If the interval contains zero, the two are **not** distinguishable at this +/// sample size, and reporting one as more concentrated than the other would be +/// reporting noise. +pub fn difference_interval( + a: &[L], + b: &[L], + replicates: usize, + seed: u64, +) -> Option { + let point = Anonymity::from_labels(a)?.loss_factor - Anonymity::from_labels(b)?.loss_factor; + + let ma = class_indices(a); + let mb = class_indices(b); + let ca = ma.iter().copied().max().map(|m| m + 1)?; + let cb = mb.iter().copied().max().map(|m| m + 1)?; + + // One generator, drawn from alternately, so the pairing is reproducible and + // the two populations cannot accidentally share a stream position. + let mut rng = SplitMix64::new(seed); + let mut draws: Vec = Vec::with_capacity(replicates); + for _ in 0..replicates { + let ra = replicate(&ma, ca, &mut rng); + let rb = replicate(&mb, cb, &mut rng); + if let (Some(x), Some(y)) = (ra, rb) { + draws.push(x - y); + } + } + if draws.is_empty() { + return None; + } + draws.sort_by(|a, b| a.partial_cmp(b).expect("no NaN in a loss factor")); + + Some(Interval { + point, + lo: percentile(&draws, 0.025), + hi: percentile(&draws, 0.975), + replicates: draws.len(), + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn labels(spec: &[(&str, usize)]) -> Vec { + let mut out = Vec::new(); + for (name, n) in spec { + for _ in 0..*n { + out.push((*name).to_string()); + } + } + out + } + + /// One class means no uncertainty: every resample is the same population, + /// so the interval must collapse onto the point. + #[test] + fn a_single_class_has_no_sampling_spread() { + let l = labels(&[("cex:binance", 40)]); + let i = loss_factor_interval(&l, 500, DEFAULT_SEED).unwrap(); + assert_eq!(i.point, 1.0); + assert_eq!(i.lo, 1.0); + assert_eq!(i.hi, 1.0); + } + + /// The interval must actually contain the estimate it surrounds. Trivial to + /// state and the first thing an off-by-one in the percentile index breaks. + #[test] + fn the_interval_contains_the_point_estimate() { + let l = labels(&[("a", 20), ("b", 10), ("c", 5), ("d", 5)]); + let i = loss_factor_interval(&l, 2_000, DEFAULT_SEED).unwrap(); + assert!( + i.lo <= i.point && i.point <= i.hi, + "point {} outside [{}, {}]", + i.point, + i.lo, + i.hi + ); + } + + /// `analyze` is a pure pass and its output is a published number. The same + /// sample and the same seed must produce the same interval, byte for byte. + #[test] + fn the_interval_is_reproducible_from_its_seed() { + let l = labels(&[("a", 12), ("b", 9), ("c", 4), ("d", 1)]); + let x = loss_factor_interval(&l, 1_000, DEFAULT_SEED).unwrap(); + let y = loss_factor_interval(&l, 1_000, DEFAULT_SEED).unwrap(); + assert_eq!(x, y); + let z = loss_factor_interval(&l, 1_000, DEFAULT_SEED ^ 1).unwrap(); + assert_ne!(x.lo, z.lo, "a different seed must actually resample"); + } + + /// Fewer members must mean a wider interval. If this ever inverts, the + /// resampling is not resampling. + #[test] + fn a_smaller_sample_gives_a_wider_interval() { + let big = labels(&[("a", 40), ("b", 30), ("c", 20), ("d", 10)]); + let small = labels(&[("a", 4), ("b", 3), ("c", 2), ("d", 1)]); + let wide = loss_factor_interval(&small, 4_000, DEFAULT_SEED).unwrap(); + let tight = loss_factor_interval(&big, 4_000, DEFAULT_SEED).unwrap(); + assert!( + wide.width() > tight.width(), + "small sample width {} not wider than large sample width {}", + wide.width(), + tight.width() + ); + } + + /// Two populations that differ starkly must separate; the interval on the + /// difference must exclude zero. + #[test] + fn a_stark_difference_separates() { + // One concentrated population and one spread population. + let concentrated = labels(&[("a", 38), ("b", 1), ("c", 1)]); + let spread = labels(&[("a", 8), ("b", 8), ("c", 8), ("d", 8), ("e", 8)]); + let d = difference_interval(&concentrated, &spread, 4_000, DEFAULT_SEED).unwrap(); + assert!( + d.point > 0.0, + "concentrated must have the higher loss factor" + ); + assert!( + d.excludes_zero(), + "a stark difference must separate: [{}, {}]", + d.lo, + d.hi + ); + } + + /// The case that matters most, and the one a careless comparison gets + /// wrong: two samples drawn from the *same* shape must NOT separate. + /// + /// Without this the tool would report every pair of populations as + /// different, which is the failure mode of comparing point estimates. + #[test] + fn two_samples_of_the_same_shape_do_not_separate() { + let a = labels(&[("a", 10), ("b", 6), ("c", 3), ("d", 1)]); + let b = labels(&[("w", 10), ("x", 6), ("y", 3), ("z", 1)]); + let d = difference_interval(&a, &b, 4_000, DEFAULT_SEED).unwrap(); + assert!( + !d.excludes_zero(), + "identically shaped populations must not separate: [{}, {}]", + d.lo, + d.hi + ); + } + + /// The generator must be uniform enough that it does not itself bias the + /// interval. A modulus over the full range would fail this at large n. + #[test] + fn the_generator_is_uniform_over_its_range() { + let mut rng = SplitMix64::new(DEFAULT_SEED); + let buckets = 7u64; + let draws = 70_000; + let mut counts = vec![0usize; buckets as usize]; + for _ in 0..draws { + counts[rng.below(buckets) as usize] += 1; + } + let expected = draws as f64 / buckets as f64; + for (i, &c) in counts.iter().enumerate() { + let dev = (c as f64 - expected).abs() / expected; + assert!(dev < 0.05, "bucket {i} deviates {:.3} from uniform", dev); + } + } + + #[test] + fn an_empty_population_yields_nothing() { + let empty: Vec = Vec::new(); + assert!(loss_factor_interval(&empty, 100, DEFAULT_SEED).is_none()); + } +} diff --git a/crates/mirror-provenance/src/lib.rs b/crates/mirror-provenance/src/lib.rs index c3e86586..0822e097 100644 --- a/crates/mirror-provenance/src/lib.rs +++ b/crates/mirror-provenance/src/lib.rs @@ -9,6 +9,7 @@ //! the limits of what the numbers prove are in `docs/PROVENANCE_METHOD.md`. #![forbid(unsafe_code)] +pub mod bootstrap; pub mod classify; pub mod edge; pub mod facts; @@ -18,6 +19,7 @@ pub mod outcome; pub mod rpc; pub mod trace; +pub use bootstrap::{difference_interval, loss_factor_interval, Interval}; pub use classify::{AnchorSet, Classifier, SetStructure, Thresholds}; pub use edge::{FundingEdge, TransactionView}; pub use facts::{AddressFacts, SigCount}; diff --git a/data/seeds-marinade.txt b/data/seeds-marinade.txt new file mode 100644 index 00000000..ae036057 --- /dev/null +++ b/data/seeds-marinade.txt @@ -0,0 +1,100 @@ +H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R +2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4 +FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf +umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ +GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG +3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr +G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1 +7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No +Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX +9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr +GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq +2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7 +okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28 +E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq +9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb +5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ +EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U +CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm +71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm +Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK +2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK +EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9 +GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i +FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq +6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG +H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx +2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764 +B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i +6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9 +93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K +4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh +GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb +FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo +AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4 +DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa +4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh +759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T +CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt +Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi +9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck +G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu +8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65 +41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR +7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt +HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw +JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n +7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg +GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA +DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z +BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ +FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G +AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa +FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA +71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip +2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh +CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG +GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9 +9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV +BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe +3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM +5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z +ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj +6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL +55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB +GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv +D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3 +3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF +DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8 +DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8 +B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc +6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV +6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv +4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL +DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv +4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3 +2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o +GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2 +9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879 +5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC +HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh +C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb +8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z +2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2 +GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs +Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK +DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN +3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi +FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p +G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2 +3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9 +BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij +GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf +AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf +83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg +CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ +4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7 +86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4 +B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM +HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K +7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd From eeb0877e7f8d42dd4f903011a8129414ebae47c5 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 02:09:44 -0300 Subject: [PATCH 41/83] fix(provenance): the birth edge is the oldest CREDIT, which is not what the code looked for Found while building a control population, and it invalidates every rho this project has published. The documented rule, in the README and in the method, is "the birth edge is the oldest credit". The implementation paged to the oldest signature, fetched that one transaction, and asked whether it credited the address. If it did not, the chain terminated as NoIncomingEdge. An address's oldest transaction is frequently not a credit. It routinely appears as a passive participant in someone else's transaction -- an ATA creation, a multisig setup, a program call that merely references it -- before it is ever funded. Two Marinade seeds checked by hand: 6,457 and 11,491 lifetime signatures, both funded, and in both cases the oldest transaction carries a delta of exactly zero for the seed while crediting two other accounts. So the tracer reported "this address has no funding credit" -- a claim about the chain -- when the truth was "we read one transaction out of eleven thousand", a claim about us. That is precisely the substitution section 6 of the method document forbids, committed by the tool that document describes. The scan now walks forward from the oldest transaction until a credit appears, bounded by a birth_scan_cap of 24. Exhausting that budget is a new outcome, BirthScanExhausted, counted as budget-unresolved and never as evidence -- kept as a separate variant precisely so "we stopped looking" can never again be tallied as "there is nothing there". Cost is unchanged for the common case: an address whose oldest transaction is its funding still takes one fetch. Every published rho was computed with the broken tracer and every affected run will be re-collected and re-reported, including the headline. --- README.md | 27 +- crates/mirror-provenance/src/facts.rs | 8 + crates/mirror-provenance/src/outcome.rs | 15 +- crates/mirror-provenance/src/trace.rs | 151 +- data/sample-marinade.json | 3539 +++++++++++++++++++++++ docs/PROVENANCE_METHOD.md | 62 + 6 files changed, 3787 insertions(+), 15 deletions(-) create mode 100644 data/sample-marinade.json diff --git a/README.md b/README.md index 6d80e4d3..8543e3a0 100644 --- a/README.md +++ b/README.md @@ -167,12 +167,13 @@ symptom — proofs that verify nowhere — points at everything except the hash. ## The measurement -Three commands, two passes, and the split is the point: +Four commands, two passes, and the split is the point: ``` mirror seeds --program # member-weighted frame, one row per depositor mirror collect --seeds seeds.txt # the only networked step; writes sample.json mirror analyze --sample sample.json # pure, offline, deterministic +mirror compare --sample a.json --against b.json # is the difference real? ``` `data/sample-privacycash-run4.json` is the committed artifact behind the @@ -216,6 +217,30 @@ weakest of them, and none of them is visible in the output. addresses because they appear in recent blocks is size-biased toward the highest-frequency actors. +### Two uncertainties, and the bias that runs against us + +A `ρ` is reported with **two** intervals, because they answer different +questions and neither covers the other: + +- the **unresolved bracket** — what if the members we could not resolve had all + been one class, or all been distinct? An exact bound. +- the **sampling interval** — these depositors are a draw from a much larger + population, so how much of `ρ` is the draw? A bootstrap over members, 10,000 + replicates at a published seed, so a reader recomputes our interval and not + merely a similar one. + +And a bias that no interval fixes: plug-in entropy is biased low when classes +are many and members few, which is every run here. Since `ρ = 2^−H(C)`, that +means **every ρ we publish is biased high — the pools plausibly leak less than +we report.** It is stated because it is the direction that makes our own number +look worse, and a bias disclosed only when it flatters is not a disclosure. + +That bias is also why `compare` exists rather than a subtraction. It falls the +same way on every population measured the same way, so a *difference* survives +what neither absolute number does — and when the interval on the difference +contains zero, the tool says the two are indistinguishable and declines to rank +them. + ### The folklore formula is inverted `2^H(C)` — entropy over the class-size distribution — is widely quoted as the diff --git a/crates/mirror-provenance/src/facts.rs b/crates/mirror-provenance/src/facts.rs index 5343ae8e..0615c0fb 100644 --- a/crates/mirror-provenance/src/facts.rs +++ b/crates/mirror-provenance/src/facts.rs @@ -52,6 +52,13 @@ pub struct AddressFacts { pub fee_payers: Vec, /// Addresses this one was the sole source of a birth edge for. Fan-out. pub funded: Vec, + /// The birth-credit scan ran out of budget before finding a credit. + /// + /// Distinguishes "we stopped looking" from "there is nothing there". Only + /// meaningful when `birth_edge` is `None`, and when it is set the address is + /// a **budget** outcome rather than evidence about the chain. + #[serde(default)] + pub birth_scan_exhausted: bool, } impl AddressFacts { @@ -65,6 +72,7 @@ impl AddressFacts { birth_edge: None, fee_payers: Vec::new(), funded: Vec::new(), + birth_scan_exhausted: false, } } diff --git a/crates/mirror-provenance/src/outcome.rs b/crates/mirror-provenance/src/outcome.rs index 785ee45f..281b26ba 100644 --- a/crates/mirror-provenance/src/outcome.rs +++ b/crates/mirror-provenance/src/outcome.rs @@ -53,6 +53,14 @@ pub enum Unresolved { /// SOL-only scope, and the address holds token accounts with inflow, so the /// funding event is structurally invisible. A scope limit, not evidence. SplBlindSpot, + /// The oldest transactions were read up to the birth-scan cap and none of + /// them credited the address. **Budget, not evidence** — the credit is + /// plausibly the next transaction along. + /// + /// Kept separate from `NoIncomingEdge` deliberately. Collapsing the two + /// would let "we stopped looking" be reported as "there is nothing there", + /// which is the exact substitution this taxonomy exists to prevent. + BirthScanExhausted, } impl Unresolved { @@ -101,6 +109,9 @@ pub struct Census { pub page_cap_hit: u64, pub rpc_failure: u64, pub spl_blind_spot: u64, + /// Budget, not evidence: the birth-credit scan ran out before finding one. + #[serde(default)] + pub birth_scan_exhausted: u64, } /// Above this rate of RPC failures a run refuses to publish a headline. @@ -121,6 +132,7 @@ impl Census { Unresolved::PageCapHit => self.page_cap_hit += 1, Unresolved::RpcFailure => self.rpc_failure += 1, Unresolved::SplBlindSpot => self.spl_blind_spot += 1, + Unresolved::BirthScanExhausted => self.birth_scan_exhausted += 1, }, } } @@ -134,6 +146,7 @@ impl Census { + self.page_cap_hit + self.rpc_failure + self.spl_blind_spot + + self.birth_scan_exhausted } /// Members whose outcome says something about the chain: everything except @@ -166,7 +179,7 @@ impl Census { self.attempted(), self.resolved, self.no_incoming_edge + self.below_threshold, - self.depth_exceeded + self.page_cap_hit, + self.depth_exceeded + self.page_cap_hit + self.birth_scan_exhausted, self.spl_blind_spot, self.rpc_failure, self.failure_rate() * 100.0 diff --git a/crates/mirror-provenance/src/trace.rs b/crates/mirror-provenance/src/trace.rs index fc816e4f..4e910978 100644 --- a/crates/mirror-provenance/src/trace.rs +++ b/crates/mirror-provenance/src/trace.rs @@ -70,6 +70,21 @@ pub struct CollectionConfig { /// refusing. pub sig_page_cap: u32, pub page_size: u32, + /// Transactions to examine, oldest first, while looking for the birth + /// *credit*. + /// + /// The rule is "the oldest credit", and an address's oldest **transaction** + /// is frequently not one: an account can appear as a passive participant in + /// someone else's transaction — an ATA creation, a multisig setup, a program + /// interaction that merely references it — before it ever receives value. + /// Reading only that one transaction and concluding "no incoming edge" + /// reports an absence of evidence where the truth is that we looked at a + /// single transaction out of thousands. + /// + /// So the walk continues forward from the oldest until a credit appears. + /// Exhausting this budget without finding one is a **budget** outcome and is + /// recorded as such, never as evidence about the address. + pub birth_scan_cap: u32, } impl Default for CollectionConfig { @@ -82,6 +97,10 @@ impl Default for CollectionConfig { min_edge_lamports: 500_000, sig_page_cap: 20, page_size: 1_000, + // Enough to clear the common cases — an ATA creation, a couple of + // program interactions — before the account is funded, without + // turning one address into a hundred transaction fetches. + birth_scan_cap: 24, } } } @@ -96,6 +115,10 @@ pub enum ChainStop { DepthExceeded, PageCapHit, RpcFailure, + /// The oldest transactions were examined up to `birth_scan_cap` and none of + /// them credited the address. A budget outcome: the credit may well exist + /// just beyond the scan. + BirthScanExhausted, } impl ChainStop { @@ -110,6 +133,7 @@ impl ChainStop { ChainStop::BelowThreshold => Unresolved::BelowThreshold, ChainStop::PageCapHit => Unresolved::PageCapHit, ChainStop::RpcFailure => Unresolved::RpcFailure, + ChainStop::BirthScanExhausted => Unresolved::BirthScanExhausted, } } } @@ -375,8 +399,15 @@ impl<'a> Collector<'a> { let facts = &self.facts[¤t]; let Some(edge) = facts.birth_edge.clone() else { + // Three different reasons there is no edge, and only one of them + // is a statement about the address. Ordered from ours to theirs: + // we never reached the oldest page; we reached it but stopped + // scanning forward before a credit appeared; or we read the + // whole early history and there genuinely is no credit. let stop = if matches!(facts.signatures, SigCount::AtLeast(_)) { ChainStop::PageCapHit + } else if facts.birth_scan_exhausted { + ChainStop::BirthScanExhausted } else { ChainStop::NoIncomingEdge }; @@ -463,6 +494,9 @@ impl<'a> Collector<'a> { let mut seen: u64 = 0; let mut oldest: Option = None; let mut last_seen_time: Option = None; + // The oldest page, kept whole. The birth credit is looked for by walking + // forward from its end, so the single oldest signature is not enough. + let mut oldest_page: Vec = Vec::new(); for page in 0..self.config.sig_page_cap { let batch = self.client.signatures_for_address( @@ -478,6 +512,7 @@ impl<'a> Collector<'a> { before = Some(last.signature.clone()); last_seen_time = last.block_time.or(last_seen_time); oldest = Some(last); + oldest_page = batch.clone(); if (batch.len() as u32) < self.config.page_size { // A short page is the end of the history: this is exact. @@ -511,22 +546,67 @@ impl<'a> Collector<'a> { } facts.first_seen = oldest.block_time; - let tx = self.client.transaction(&oldest.signature)?; - // The fee payer is the first key, and it is what fee-payer clustering - // groups on. - if let Some(payer) = tx.account_keys.first() { - if !facts.fee_payers.iter().any(|p| p == payer) { - facts.fee_payers.push(payer.clone()); + // Walk forward from the oldest transaction until one credits the + // address. The oldest transaction is very often not a credit — an + // account routinely appears as a passive participant in someone else's + // transaction before it is ever funded — and stopping there reports "no + // incoming edge" for an address that plainly has one. + let scanned = self.scan_for_birth_credit(address, &oldest_page, facts)?; + facts.birth_scan_exhausted = facts.birth_edge.is_none() && scanned >= self.birth_budget(); + Ok(()) + } + + fn birth_budget(&self) -> usize { + self.config.birth_scan_cap as usize + } + + /// Examines up to `birth_scan_cap` transactions, oldest first, and records + /// the first that credits `address`. + /// + /// Returns how many were examined, so the caller can tell "there is no + /// credit here" from "we stopped looking". Those must never be the same + /// outcome. + fn scan_for_birth_credit( + &mut self, + address: &str, + oldest_page: &[crate::rpc::SignatureInfo], + facts: &mut AddressFacts, + ) -> Result { + let budget = self.birth_budget(); + let mut examined = 0usize; + + // `oldest_page` is newest-first, so walking it in reverse is oldest-first. + for info in oldest_page.iter().rev() { + if examined >= budget { + break; } - } - if let Some(edge) = tx.edge_crediting(address) { - self.edges_seen += 1; - if edge.ambiguous_attribution { - self.ambiguous += 1; + if info.err { + // A failed transaction moved no value. It costs nothing to skip + // and is not worth a fetch. + continue; + } + examined += 1; + let tx = self.client.transaction(&info.signature)?; + + // Fee-payer clustering wants the payer of the address's earliest + // transactions, so record it for every one examined rather than + // only for the one that happens to carry the credit. + if let Some(payer) = tx.account_keys.first() { + if !facts.fee_payers.iter().any(|p| p == payer) { + facts.fee_payers.push(payer.clone()); + } + } + + if let Some(edge) = tx.edge_crediting(address) { + self.edges_seen += 1; + if edge.ambiguous_attribution { + self.ambiguous += 1; + } + facts.birth_edge = Some(edge); + return Ok(examined); } - facts.birth_edge = Some(edge); } - Ok(()) + Ok(examined) } } @@ -695,11 +775,56 @@ mod tests { (ChainStop::DepthExceeded, Unresolved::DepthExceeded), (ChainStop::PageCapHit, Unresolved::PageCapHit), (ChainStop::RpcFailure, Unresolved::RpcFailure), + ( + ChainStop::BirthScanExhausted, + Unresolved::BirthScanExhausted, + ), ] { assert_eq!(stop.as_unresolved(), expected, "{stop:?} mapped wrongly"); } } + /// Running out of birth-scan budget is **our** limit, so it must never be + /// counted as evidence about the address, and it must never be collapsed + /// into `NoIncomingEdge`. + /// + /// The distinction is the entire point: one says "this address has no + /// funding credit", which is a claim about the chain, and the other says + /// "we stopped reading", which is a claim about us. + #[test] + fn an_exhausted_birth_scan_is_budget_and_not_evidence() { + assert!(!Unresolved::BirthScanExhausted.is_evidence()); + assert!(!Unresolved::BirthScanExhausted.is_failure()); + + let mut seed = wallet("member"); + seed.birth_scan_exhausted = true; + let sample = sample_with( + vec![Chain { + seed: "member".into(), + visited: vec!["member".into()], + stop: ChainStop::BirthScanExhausted, + }], + vec![seed], + ); + let (_, census) = + classify_sample(&sample, &AnchorSet::default(), &Thresholds::default(), NOW); + assert_eq!(census.birth_scan_exhausted, 1); + assert_eq!( + census.no_incoming_edge, 0, + "a budget outcome leaked into the evidence bucket" + ); + assert!( + census.summary().contains("budget-unresolved 1"), + "must be reported as budget: {}", + census.summary() + ); + assert!( + census.summary().contains("evidence-unresolved 0"), + "must not be reported as evidence: {}", + census.summary() + ); + } + #[test] fn an_rpc_failure_stays_a_failure_and_is_excluded_from_the_distribution() { let sample = sample_with( diff --git a/data/sample-marinade.json b/data/sample-marinade.json new file mode 100644 index 00000000..23d62651 --- /dev/null +++ b/data/sample-marinade.json @@ -0,0 +1,3539 @@ +{ + "manifest": { + "endpoint": "https://mainnet.helius-rpc.com", + "first_available_block": 0, + "archival_probe_ok": true, + "collected_at": 1785041387, + "config": { + "scope": "Sol", + "depth_max": 16, + "min_edge_lamports": 500000, + "sig_page_cap": 24, + "page_size": 1000 + }, + "thresholds_hub_signatures": 5000, + "thresholds_distributor_fanout": 20, + "thresholds_cluster_min_size": 3, + "rpc_calls": 1357, + "excluded_non_wallet": [ + "71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip", + "DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8", + "DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv", + "Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK", + "DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN", + "3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi", + "83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg", + "7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd" + ], + "ambiguous_attribution_rate": 0.0 + }, + "chains": [ + { + "seed": "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "visited": [ + "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "PageCapHit" + }, + { + "seed": "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "visited": [ + "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "visited": [ + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "visited": [ + "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "visited": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "visited": [ + "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" + ], + "stop": "PageCapHit" + }, + { + "seed": "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "visited": [ + "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "visited": [ + "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "visited": [ + "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "stop": "PageCapHit" + }, + { + "seed": "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "visited": [ + "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw" + ], + "stop": "PageCapHit" + }, + { + "seed": "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "visited": [ + "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi" + ], + "stop": "LocalTerminal" + }, + { + "seed": "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "visited": [ + "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "visited": [ + "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "visited": [ + "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw" + ], + "stop": "LocalTerminal" + }, + { + "seed": "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "visited": [ + "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ", + "visited": [ + "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ" + ], + "stop": "PageCapHit" + }, + { + "seed": "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "visited": [ + "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "visited": [ + "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4" + ], + "stop": "PageCapHit" + }, + { + "seed": "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "visited": [ + "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE", + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH", + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "stop": "PageCapHit" + }, + { + "seed": "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "visited": [ + "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm" + ], + "stop": "PageCapHit" + }, + { + "seed": "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "visited": [ + "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "visited": [ + "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i", + "visited": [ + "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i" + ], + "stop": "PageCapHit" + }, + { + "seed": "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq", + "visited": [ + "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq" + ], + "stop": "PageCapHit" + }, + { + "seed": "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "visited": [ + "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "visited": [ + "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "visited": [ + "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "visited": [ + "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "visited": [ + "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "visited": [ + "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "visited": [ + "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "visited": [ + "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1" + ], + "stop": "PageCapHit" + }, + { + "seed": "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "visited": [ + "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "visited": [ + "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw" + ], + "stop": "PageCapHit" + }, + { + "seed": "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "visited": [ + "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn" + ], + "stop": "PageCapHit" + }, + { + "seed": "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "visited": [ + "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "visited": [ + "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "visited": [ + "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "visited": [ + "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "stop": "PageCapHit" + }, + { + "seed": "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "visited": [ + "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL" + ], + "stop": "PageCapHit" + }, + { + "seed": "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "visited": [ + "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "visited": [ + "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "visited": [ + "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "visited": [ + "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "visited": [ + "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "visited": [ + "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP", + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "stop": "PageCapHit" + }, + { + "seed": "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "visited": [ + "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4", + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "stop": "PageCapHit" + }, + { + "seed": "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA", + "visited": [ + "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA" + ], + "stop": "PageCapHit" + }, + { + "seed": "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z", + "visited": [ + "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z" + ], + "stop": "PageCapHit" + }, + { + "seed": "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "visited": [ + "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G", + "visited": [ + "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G" + ], + "stop": "PageCapHit" + }, + { + "seed": "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "visited": [ + "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "visited": [ + "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" + ], + "stop": "PageCapHit" + }, + { + "seed": "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "visited": [ + "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "stop": "PageCapHit" + }, + { + "seed": "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "visited": [ + "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z", + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG" + ], + "stop": "PageCapHit" + }, + { + "seed": "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "visited": [ + "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "visited": [ + "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "visited": [ + "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "visited": [ + "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "visited": [ + "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "visited": [ + "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "visited": [ + "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "visited": [ + "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "visited": [ + "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "visited": [ + "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "visited": [ + "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "visited": [ + "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "visited": [ + "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "visited": [ + "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq", + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + ], + "stop": "PageCapHit" + }, + { + "seed": "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "visited": [ + "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "visited": [ + "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "visited": [ + "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "visited": [ + "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW" + ], + "stop": "PageCapHit" + }, + { + "seed": "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "visited": [ + "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "visited": [ + "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "visited": [ + "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "visited": [ + "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" + ], + "stop": "PageCapHit" + }, + { + "seed": "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb", + "visited": [ + "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb" + ], + "stop": "PageCapHit" + }, + { + "seed": "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "visited": [ + "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "stop": "PageCapHit" + }, + { + "seed": "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "visited": [ + "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "visited": [ + "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS" + ], + "stop": "PageCapHit" + }, + { + "seed": "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "visited": [ + "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "visited": [ + "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "visited": [ + "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "visited": [ + "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "visited": [ + "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "visited": [ + "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ", + "visited": [ + "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ" + ], + "stop": "PageCapHit" + }, + { + "seed": "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "visited": [ + "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "visited": [ + "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM", + "visited": [ + "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM" + ], + "stop": "NoIncomingEdge" + }, + { + "seed": "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "visited": [ + "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K" + ], + "stop": "NoIncomingEdge" + } + ], + "facts": { + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm": { + "address": "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784759973, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi" + ] + }, + "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4": { + "address": "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 6457 + }, + "first_seen": 1756313602, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL": { + "address": "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783798536, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck" + ] + }, + "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh": { + "address": "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 80 + }, + "first_seen": 1752673276, + "birth_edge": { + "sink": "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "sources": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "value": 12063418500, + "signature": "cKPChkvMEjtfwWt9C9UT6huX3cCp543m4ywRXBuBsuYWNKrDuvTCaJqSFFUym2mvuXMG1xCqWhQqvgUDjinFVJb", + "slot": 353692459, + "block_time": 1752673276, + "ambiguous_attribution": false + }, + "fee_payers": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "funded": [] + }, + "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK": { + "address": "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7945 + }, + "first_seen": 1774268614, + "birth_edge": { + "sink": "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "sources": [ + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ" + ], + "value": 5000000000, + "signature": "3D9VPk2WSdFidXqT2yc7QmN4so5tcRG8oq1RMMVN4Y81op9PcSrNPPhukqLvU2fmRPAncjyFp7csZxbee2uBg8qw", + "slot": 408325661, + "block_time": 1774268614, + "ambiguous_attribution": false + }, + "fee_payers": [ + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ" + ], + "funded": [] + }, + "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o": { + "address": "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 72 + }, + "first_seen": 1767875809, + "birth_edge": { + "sink": "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "sources": [ + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW" + ], + "value": 50000000, + "signature": "jAurnGX1dLaqmmcS95zSb1N2XZHj65BdUNAtchot1MWmx3z6We5f8Jew1sGu9NrQFrWGR8QdkMCSWSUAfu1yahT", + "slot": 392132125, + "block_time": 1767875809, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW" + ], + "funded": [] + }, + "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7": { + "address": "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 33 + }, + "first_seen": 1778858822, + "birth_edge": { + "sink": "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "sources": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG" + ], + "value": 10000000, + "signature": "41PgkwaTASYRwAhAmSQ6VMERq4xCjh5YNSsjmzdbU5VukKn6iMn1nzoyswB2wsJJVehVwKLspNmg4RpCKLptEtxL", + "slot": 419929899, + "block_time": 1778858822, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG" + ], + "funded": [] + }, + "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764": { + "address": "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 74 + }, + "first_seen": 1768845271, + "birth_edge": { + "sink": "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "sources": [ + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb" + ], + "value": 4000000000, + "signature": "buG8ygWB6h5RLJmX5caSxTXK786wYRXgwfS9RFABnCivqy6yNePkzmn3Hsyr3XVhs28D1qWgbXbThbyZUzymN6p", + "slot": 394584868, + "block_time": 1768845271, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb" + ], + "funded": [] + }, + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv": { + "address": "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 351 + }, + "first_seen": 1715877601, + "birth_edge": { + "sink": "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 400000000, + "signature": "3vMHgLqfkYX6fLwgJKkv34rnViBSYsD2mwRJx5b2gBnMHjHemR7aMm4e12V7pwYUfDNDTNSctZ132e3GAixT72F8", + "slot": 266156059, + "block_time": 1715877601, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ" + ] + }, + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S": { + "address": "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1655911154, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z" + ] + }, + "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2": { + "address": "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 34 + }, + "first_seen": 1760264183, + "birth_edge": null, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4": { + "address": "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1710631332, + "birth_edge": { + "sink": "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4", + "sources": [ + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39" + ], + "value": 394037978, + "signature": "3tJT334qtxVLAYkbJ8nGePCKiT4k9nP2DtwPgjwLiQuMbCMcMv6awuiXC19xq9zR9DUM3TAenySiEdiyp2uMygwB", + "slot": 254612541, + "block_time": 1710631332, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39" + ], + "funded": [ + "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg" + ] + }, + "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM": { + "address": "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 270 + }, + "first_seen": 1649945488, + "birth_edge": { + "sink": "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 11854550000, + "signature": "5LojLQtWF5mFYcGE1MiSjmeixsnYuPneTWNigfaeMGJ1Yysw88ct5X7eaJdhRTy482fAXSM97EpWV6YhYBeBHQ7n", + "slot": 129710493, + "block_time": 1649945488, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [] + }, + "3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi": { + "address": "3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr": { + "address": "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1785 + }, + "first_seen": 1739523472, + "birth_edge": { + "sink": "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "sources": [ + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" + ], + "value": 1987201900, + "signature": "bzNe8Zg7oE1fXGipW89PL11Q3LbWTdjkKzFGFfSHFnM1LZr93MSbyf5JhPNZ7khZ6twbx9RF41WCFySKZEj14NX", + "slot": 320565860, + "block_time": 1739523472, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" + ], + "funded": [] + }, + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw": { + "address": "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1779165822, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr" + ] + }, + "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9": { + "address": "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 254 + }, + "first_seen": 1723048172, + "birth_edge": null, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF": { + "address": "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2386 + }, + "first_seen": 1770200497, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR": { + "address": "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 25 + }, + "first_seen": 1782895333, + "birth_edge": { + "sink": "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "sources": [ + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ" + ], + "value": 734553600, + "signature": "36vjnxndxfYcSHBf37rrdBPqcwc2BBpECPGQzSr6RNhbizneiaCKc4xXpiMNNGtk6eqnexRme4xfy3GDLfkPNyRk", + "slot": 430058672, + "block_time": 1782895333, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ" + ], + "funded": [] + }, + "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL": { + "address": "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18 + }, + "first_seen": 1758306486, + "birth_edge": { + "sink": "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "sources": [ + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep" + ], + "value": 2100000000, + "signature": "57LKR9sEPC21YHt2pbje7Ur5ACGLqqMUSF5wHYdquAVozDKs3K516nGr5sa47qJdKZiVvQnxTms11orYiYb7uvCP", + "slot": 367913728, + "block_time": 1758306486, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep" + ], + "funded": [] + }, + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw": { + "address": "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784775886, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4" + ] + }, + "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7": { + "address": "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5237 + }, + "first_seen": 1757495135, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3": { + "address": "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 119 + }, + "first_seen": 1756133000, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh": { + "address": "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20761 + }, + "first_seen": 1663879176, + "birth_edge": { + "sink": "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 490000000, + "signature": "5kDrBVEWkieCWXHps4mJ9h437RmD7D9Nmyo2uo2UwQsDNUWm6KiAB8T3DNQ8svrsbx11hqdXKcUBNQAY1gACJNwT", + "slot": 151875320, + "block_time": 1663879176, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh": { + "address": "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20 + }, + "first_seen": 1783099754, + "birth_edge": null, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [] + }, + "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB": { + "address": "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 52 + }, + "first_seen": 1770266528, + "birth_edge": { + "sink": "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1204809220, + "signature": "373hVK6YWozQ1rjpFwQ9EYjzYKW7AJ3YEpfyAPGiDtdeMmRphi1bKSpnPn2vu8t8t7J7fKAug14jQu6LdRNUR4Jo", + "slot": 398150397, + "block_time": 1770266528, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ": { + "address": "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1778800198, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1": { + "address": "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784580231, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb" + ] + }, + "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC": { + "address": "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 824 + }, + "first_seen": 1757568229, + "birth_edge": null, + "fee_payers": [ + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN" + ], + "funded": [] + }, + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD": { + "address": "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1742045493, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr" + ] + }, + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt": { + "address": "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "owner": null, + "executable": false, + "signatures": { + "Exact": 124 + }, + "first_seen": 1773144352, + "birth_edge": { + "sink": "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 99000000, + "signature": "NbnA7s6aJWNM2nfCACoXNDeVGZwALbRcFCkM33u7mQyMKJ3QpSHhA1pVPVX3EugPo74gm1ycaL1CBJ4UqcN3dXi", + "slot": 405469329, + "block_time": 1773144352, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3" + ] + }, + "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z": { + "address": "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 96 + }, + "first_seen": 1628597237, + "birth_edge": { + "sink": "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 4990000000, + "signature": "2kUPLWWUJut6M2AfRAZAiGLkuWLJN5D4JXMZ1VKr7cj7uWWmmcY43cGMHS7UMF8Uw32rrFUdCMNh6AMyJKR7woy9", + "slot": 91034738, + "block_time": 1628597237, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [] + }, + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ": { + "address": "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1782843699, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR" + ] + }, + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9": { + "address": "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784934009, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2" + ] + }, + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39": { + "address": "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1710624873, + "birth_edge": { + "sink": "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "sources": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "value": 394042978, + "signature": "5reS92mjj8Avu1Hm8NibK41HwVLTaetYhYEb1eGpT3AYkssVwhBEq479YqaVAw7G4tPhrnwvjUai3ruhQLpUW72T", + "slot": 254597447, + "block_time": 1710624873, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "funded": [ + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4" + ] + }, + "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL": { + "address": "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 66 + }, + "first_seen": 1730986163, + "birth_edge": { + "sink": "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "sources": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "value": 240234545, + "signature": "4JGR17CZKiUrUKXVpnbhu4LzPvRc1vMRChQVcqLpjuWaPswEvUS3b8Cjf5tyNyuptuA5GGBrTt2cCgdv1QsYuwiQ", + "slot": 300009349, + "block_time": 1730986163, + "ambiguous_attribution": false + }, + "fee_payers": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "funded": [] + }, + "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv": { + "address": "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7163 + }, + "first_seen": 1776453682, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9": { + "address": "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 41 + }, + "first_seen": 1771265328, + "birth_edge": { + "sink": "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1465532000, + "signature": "48jcsENEjMVCHrSfUqXLDZDt8nqQfHqDR1Fhx4VH3wGT5Epbks18Mfcs5WJ3yhzdnuWVupAUKPu6jgFTArDxM7iT", + "slot": 400700540, + "block_time": 1771265328, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV": { + "address": "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 22 + }, + "first_seen": 1781373138, + "birth_edge": { + "sink": "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "sources": [ + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq" + ], + "value": 19650094, + "signature": "5JwERz8A9ssUFHSgXzszdjoKkSs8WVRrjdmKqSRXeD7GcBCyDXqzgMjHQdtqUF9MgCA9R7cK4V7Vm9thS5ib7Fkg", + "slot": 426247804, + "block_time": 1781373138, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq" + ], + "funded": [] + }, + "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG": { + "address": "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1780571139, + "birth_edge": null, + "fee_payers": [ + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS" + ], + "funded": [] + }, + "71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip": { + "address": "71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm": { + "address": "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784211013, + "birth_edge": { + "sink": "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "sources": [ + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE" + ], + "value": 3895835199, + "signature": "4pntPEhmoeiCTqii4XrvYMiAG2VVja2htxSyFFuda7S8MZfTLjQCkV9XmNyCSqekAzURhwHxAxSEMMApwkM6Zayj", + "slot": 433287475, + "block_time": 1784211013, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE" + ], + "funded": [] + }, + "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T": { + "address": "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 26 + }, + "first_seen": 1783100066, + "birth_edge": null, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [] + }, + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ": { + "address": "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2483 + }, + "first_seen": 1642454259, + "birth_edge": null, + "fee_payers": [ + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ" + ], + "funded": [ + "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK" + ] + }, + "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No": { + "address": "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4949 + }, + "first_seen": 1756672617, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW": { + "address": "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1769937907, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o" + ] + }, + "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg": { + "address": "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18410 + }, + "first_seen": 1710632250, + "birth_edge": { + "sink": "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "sources": [ + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4" + ], + "value": 394012978, + "signature": "3bvxtDsw7CBv3imPLoMrdyhA8cjSRRP3XCwzuJfyzx5AWZ9B8MjPdU3Gju47KgKxqGcd3gLP1ZhLVnHdhaZwQqYE", + "slot": 254614672, + "block_time": 1710632250, + "ambiguous_attribution": false + }, + "fee_payers": [ + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4" + ], + "funded": [] + }, + "7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd": { + "address": "7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt": { + "address": "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1777777000, + "birth_edge": { + "sink": "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 9000000, + "signature": "48JSB2pqTRMXWg2S6ajewFiuyXADPk9q8QJYD1NLxSYwoBZzSFWKcPEKzQwN2K4dHJ6QYS8ppfo8Acra1LDZkffb", + "slot": 417226530, + "block_time": 1777777000, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg": { + "address": "83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4": { + "address": "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 650 + }, + "first_seen": 1764161875, + "birth_edge": null, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65": { + "address": "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1243 + }, + "first_seen": 1760985667, + "birth_edge": null, + "fee_payers": [ + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6" + ], + "funded": [] + }, + "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z": { + "address": "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 510 + }, + "first_seen": 1736141750, + "birth_edge": { + "sink": "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "sources": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "value": 10450531000, + "signature": "5NTDt6Rswtrvt42AT1trjiRspELnF9t98z5H2TyQ4D1L7zB84w3GQjF6borcJLChNK89mRwQUTsqpowEU6MMp4yQ", + "slot": 312181544, + "block_time": 1736141750, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "funded": [] + }, + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS": { + "address": "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1773493201, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs" + ] + }, + "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K": { + "address": "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4311 + }, + "first_seen": 1729158047, + "birth_edge": null, + "fee_payers": [ + "98tv7iW1D1prUGNX6VsCfR878jfMoHs1cF1vdMc6TYoq" + ], + "funded": [] + }, + "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck": { + "address": "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 37 + }, + "first_seen": 1781689177, + "birth_edge": { + "sink": "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "sources": [ + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL" + ], + "value": 2000000000, + "signature": "2kW6Rf9kPJAZnCSgHodJwFNifHr5L1UN3Cj8afcoWHsxHKWFiDEfzFv7v8DsvNL5HFQeihiYy4GWieL826rQnH9Y", + "slot": 427044455, + "block_time": 1781689177, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL" + ], + "funded": [] + }, + "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr": { + "address": "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1782983366, + "birth_edge": { + "sink": "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "sources": [ + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw" + ], + "value": 65793663482, + "signature": "4uU5EhqsUfPVzpZ1jHMrg9T2TgfcMWE6s81PwMypZnVXQzjKNiB4ibWb9E2MPHa5WFC5mkZhf2DN4PRtX5zaCnB3", + "slot": 430277120, + "block_time": 1782983366, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw" + ], + "funded": [] + }, + "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb": { + "address": "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 87 + }, + "first_seen": 1761613462, + "birth_edge": { + "sink": "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 5504330, + "signature": "4X7t1Ls8Zpb3bqFZchjukeaG9Tf1MdMwUS62Ua2Nx4ALiEYLR4aSCr2Az9hTVF17HBnJsPUa4Q2amcmFnXWECY9m", + "slot": 376252987, + "block_time": 1761613462, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [] + }, + "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879": { + "address": "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 59 + }, + "first_seen": 1760849092, + "birth_edge": null, + "fee_payers": [ + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "funded": [] + }, + "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV": { + "address": "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1764737043, + "birth_edge": null, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR": { + "address": "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784452952, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh" + ] + }, + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2": { + "address": "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783242816, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z" + ] + }, + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE": { + "address": "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 27 + }, + "first_seen": 1726629672, + "birth_edge": { + "sink": "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE", + "sources": [ + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH" + ], + "value": 1000000000, + "signature": "4DvouS9aXw7ir6N3ohxgKFw3UugY7jqK3aextkEtbox1i1wue5Xh4W6NHbZE9bWvZLMb1P28LoMDY4QhBnRXcehr", + "slot": 290465228, + "block_time": 1726629672, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH" + ], + "funded": [ + "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm" + ] + }, + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP": { + "address": "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 8186 + }, + "first_seen": 1711915637, + "birth_edge": { + "sink": "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP", + "sources": [ + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V" + ], + "value": 200000000, + "signature": "2sbPicuC4gBQYMAM312LbgX1eDfY9nWY1Aeomg7RBbYVq4ZBuxkFJ1Cpc1icLyVxYUvzPmUyBdn5tCjBbBgRco49", + "slot": 257602531, + "block_time": 1711915637, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V" + ], + "funded": [ + "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n" + ] + }, + "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa": { + "address": "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 81 + }, + "first_seen": 1712036070, + "birth_edge": { + "sink": "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 270768410, + "signature": "4MjcwLDx2bPhGuHdRFGKiE3khiQoSd5DWZEw7RNbuHTubuJ6MvHtZyG4TA7TTAHc9CeK6yHS68ErF5gZdeKnDC2R", + "slot": 257873921, + "block_time": 1712036070, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX": { + "address": "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3735 + }, + "first_seen": 1760474300, + "birth_edge": { + "sink": "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "sources": [ + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD" + ], + "value": 100000000, + "signature": "aq7ps7k6RZcBWVMaHcqHLVemRNLYzFQukxseeeApwgmVCZ39LNCa2kh8WLCkkdM21AUUjVv9sYtYmi9Gz9BwkUB", + "slot": 373387624, + "block_time": 1760474300, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD" + ], + "funded": [] + }, + "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4": { + "address": "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 37 + }, + "first_seen": 1775710580, + "birth_edge": { + "sink": "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "sources": [ + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw" + ], + "value": 4639648909, + "signature": "3yQYxKyRPrcH6pk87zfhx67eL6Wzza7hDrE91MA3xHKBRcWv6fHLYeW2JfwTtG2sHw5u67qig7NJ2usnX26aqb6t", + "slot": 412000930, + "block_time": 1775710580, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw" + ], + "funded": [] + }, + "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf": { + "address": "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 450 + }, + "first_seen": 1713195781, + "birth_edge": { + "sink": "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "sources": [ + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ" + ], + "value": 10000000, + "signature": "4CEipGPf7m8m8EfHLDWVTy1TsiKyD1LsD6ZAaaNNgvwWJdvqGxRoo9cPcFbASE47phKqZFzaG9dWKaTCFx99bTSb", + "slot": 260290481, + "block_time": 1713195781, + "ambiguous_attribution": false + }, + "fee_payers": [ + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ" + ], + "funded": [] + }, + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS": { + "address": "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784771253, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p" + ] + }, + "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc": { + "address": "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 64 + }, + "first_seen": 1778266848, + "birth_edge": null, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i": { + "address": "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 290 + }, + "first_seen": 1757844826, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn": { + "address": "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784572075, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa" + ] + }, + "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM": { + "address": "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 490 + }, + "first_seen": 1757104043, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ": { + "address": "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 440 + }, + "first_seen": 1773406077, + "birth_edge": null, + "fee_payers": [ + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV" + ], + "funded": [] + }, + "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij": { + "address": "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2605 + }, + "first_seen": 1753207964, + "birth_edge": { + "sink": "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "sources": [ + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD" + ], + "value": 140590159, + "signature": "2oaGcywpLPt8xEY7w84WSw4Z94DJiZgz1sTYzpCBbfT8sc5UYD99TiHCZoJqasjmLGzqUbffbHXg7LimmCk5pzu4", + "slot": 355044244, + "block_time": 1753207964, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD" + ], + "funded": [] + }, + "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe": { + "address": "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 320 + }, + "first_seen": 1720840348, + "birth_edge": { + "sink": "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 11000000000, + "signature": "1rHtsQspU78fmcbJZDfTh4hrN8xcRLjz2Ji2N7VV5KPBW29nATa6FSmt6WsnrFkmnNbz7QmuQiCDEgeRJYieX9E", + "slot": 277240691, + "block_time": 1720840348, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU": { + "address": "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784918504, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD" + ] + }, + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6": { + "address": "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784392003, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH" + ] + }, + "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb": { + "address": "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1742345740, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg": { + "address": "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2175 + }, + "first_seen": 1656406760, + "birth_edge": null, + "fee_payers": [ + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP" + ], + "funded": [ + "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL" + ] + }, + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb": { + "address": "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "owner": null, + "executable": false, + "signatures": { + "Exact": 8 + }, + "first_seen": 1768750363, + "birth_edge": { + "sink": "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 30193550020, + "signature": "4sv5REU2obsMfmgbTmSqWgiGiCnQJibid6PgNptnSpqaDLjAWGBdfotB1yiG3tnDuJv7mqRN5NyXQAstjw1QfMdr", + "slot": 394345708, + "block_time": 1768750363, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [ + "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764" + ] + }, + "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm": { + "address": "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 143 + }, + "first_seen": 1767909246, + "birth_edge": { + "sink": "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "sources": [ + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4" + ], + "value": 1310000000, + "signature": "3kDnzFaFpXEegPizm9MKtpGGJ8BnETNFdYFJpd2rxKux7Z8EF58v5sMGsnqHfGAxoPTkiZCcSUUb9VpqWL7LD4og", + "slot": 392216036, + "block_time": 1767909246, + "ambiguous_attribution": false + }, + "fee_payers": [ + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4" + ], + "funded": [] + }, + "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ": { + "address": "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1763677438, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD": { + "address": "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1058 + }, + "first_seen": 1734856327, + "birth_edge": null, + "fee_payers": [ + "HgsxLyn8175xEwRffPRN3DeARE2EVcEeXENr12HpadL6" + ], + "funded": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG" + ] + }, + "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG": { + "address": "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 36 + }, + "first_seen": 1778449810, + "birth_edge": { + "sink": "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "sources": [ + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z" + ], + "value": 200000000, + "signature": "4XRjYBMjWrSKs8PTEPYrTgkfwVw8Er4AjCYaiHtVn9L5tRWTGu7HnxBTSKUc6gGYGoLri2jr3U9dJ3HnvDvnz1HE", + "slot": 418909903, + "block_time": 1778449810, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z" + ], + "funded": [] + }, + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG": { + "address": "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1781062219, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z" + ] + }, + "Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK": { + "address": "Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt": { + "address": "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 17 + }, + "first_seen": 1783210007, + "birth_edge": { + "sink": "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 2290497690, + "signature": "3dZV999nzMy8zrpKZkKbhczT6DLA7J9YhLZZZKpFD6U6UoGg9oEUrySMR4VJNGeV6UTJ8rKbq6qLpL3AQ91UWu5S", + "slot": 430838822, + "block_time": 1783210007, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [] + }, + "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj": { + "address": "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 126 + }, + "first_seen": 1775679727, + "birth_edge": null, + "fee_payers": [ + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL" + ], + "funded": [] + }, + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c": { + "address": "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 35 + }, + "first_seen": 1775705585, + "birth_edge": null, + "fee_payers": [ + "4PqrAP81NRSgbeBNG3scWVev8RAiy1B5SgRDDo3Ub6Eu" + ], + "funded": [ + "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28" + ] + }, + "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3": { + "address": "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 87 + }, + "first_seen": 1775659926, + "birth_edge": { + "sink": "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "sources": [ + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt" + ], + "value": 57015551, + "signature": "4U4QtmZTpQ15CfqzPENGoU8QKmkR4M3MzAChG1ff7qZQVRFcizNEfrp1JqoaZaAYJSYnvZB9z1fh5hs3rD2Qxa22", + "slot": 411872556, + "block_time": 1775659926, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt" + ], + "funded": [] + }, + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD": { + "address": "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 159 + }, + "first_seen": 1759568115, + "birth_edge": { + "sink": "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "sources": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "value": 8793000, + "signature": "21n3KFdeWHq3Xb5KkQL5N9gb29bMcU9zgkBmHFK2EnKqpMCpNfhTmMC14oLPRoFMzrcrrUUp34sb1a7ehfKFByE1", + "slot": 371102052, + "block_time": 1759568115, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "funded": [ + "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX" + ] + }, + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf": { + "address": "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784789318, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh" + ] + }, + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep": { + "address": "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 394 + }, + "first_seen": 1746944587, + "birth_edge": null, + "fee_payers": [ + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri" + ], + "funded": [ + "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL" + ] + }, + "DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv": { + "address": "DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8": { + "address": "DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN": { + "address": "DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8": { + "address": "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 53 + }, + "first_seen": 1781684439, + "birth_edge": null, + "fee_payers": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "funded": [] + }, + "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z": { + "address": "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1760102727, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK": { + "address": "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1190 + }, + "first_seen": 1747220417, + "birth_edge": { + "sink": "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "sources": [ + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm" + ], + "value": 3141592653, + "signature": "3mXJiiZA35B1Y6UUyqkynZBkYe8sM4WsjzRBgSfjrvqYRhSRfWSx25Xdr5ct4mFR2UpbTJQg1goGscDGfRa3NdE3", + "slot": 339946664, + "block_time": 1747220417, + "ambiguous_attribution": false + }, + "fee_payers": [ + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm" + ], + "funded": [] + }, + "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi": { + "address": "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 200 + }, + "first_seen": 1653138893, + "birth_edge": { + "sink": "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "sources": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "value": 606489437, + "signature": "62pVXVgr23rpDrywbMT9my8PUPDsRf53biB4QSm9oa67zvw14JmuaqbuC5obnSjSYS1acXMQQRgUXVYpX2s1RHaF", + "slot": 134659485, + "block_time": 1653138893, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "funded": [] + }, + "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa": { + "address": "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1783201670, + "birth_edge": { + "sink": "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "sources": [ + "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn" + ], + "value": 185933290, + "signature": "NX3DrMfLgUE4yHC8dgbQj2vHFKiCzTrPhfTDShToZdn8skzKWLJyzEEh6dbWxeMTBEkRhqWkXVRNzmZDvGKs3Yc", + "slot": 430818102, + "block_time": 1783201670, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3ddCq8LgnyYiJFfupiNAR8rQ9BshepSMzSwWbNLageTx" + ], + "funded": [] + }, + "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq": { + "address": "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 17 + }, + "first_seen": 1784199361, + "birth_edge": { + "sink": "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "sources": [ + "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw" + ], + "value": 23682958, + "signature": "2ChU8gFx2ffsnkR6HwHJsD2Pd44iTWQ37rdfWgHwbVjMYb45X88VvqNSCaZu6vh2fgC62bXvkt6C8v9YzUrjNz26", + "slot": 433259174, + "block_time": 1784199361, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9": { + "address": "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20 + }, + "first_seen": 1782002060, + "birth_edge": { + "sink": "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 50000000, + "signature": "RtgJqd5sHDiondx4Z2yiq5essG1zXj5XwroXbZrtjkhy9NDPjutCezrPBr122sZqphKBa9YWJF3VPxiBUerRjBq", + "slot": 427833377, + "block_time": 1782002060, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [] + }, + "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U": { + "address": "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 10 + }, + "first_seen": 1781081436, + "birth_edge": { + "sink": "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 92849110, + "signature": "2d8HpMfLa5r1S3vdLKNiT69B7gPFjj4U3YHRc3skWQFqF3T2LUjTQzjPtWiHzQytw3VupQMQFiajPmjNYzN5rZfe", + "slot": 425512751, + "block_time": 1781081436, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH": { + "address": "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1703354625, + "birth_edge": { + "sink": "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH", + "sources": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "value": 1320200000, + "signature": "3cqmB1Yi2Z7ZVQjjA37NQ4d84ViimuKf1h83J23digAMRGoJMY8VmUbNkSxyqxRqsE3qBoBFKwCMXfxhVMEGNacP", + "slot": 237597989, + "block_time": 1703354625, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "funded": [ + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE" + ] + }, + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe": { + "address": "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1785033307, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R" + ] + }, + "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo": { + "address": "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13145 + }, + "first_seen": 1719672752, + "birth_edge": null, + "fee_payers": [ + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r" + ], + "funded": [] + }, + "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G": { + "address": "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784837372, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq": { + "address": "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1775881597, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf": { + "address": "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11491 + }, + "first_seen": 1770490600, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA": { + "address": "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 120 + }, + "first_seen": 1769087495, + "birth_edge": { + "sink": "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "sources": [ + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" + ], + "value": 430752000, + "signature": "2AFuX4tvKhsFguYnNfiW4QCHsYFeoARyYNbm9hMxqKHpgaX2f6d8qHfioWQsnyDCy5wFHzeUAXoSURUyGDGgmxzj", + "slot": 395193315, + "block_time": 1769087495, + "ambiguous_attribution": false + }, + "fee_payers": [ + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" + ], + "funded": [] + }, + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD": { + "address": "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1211 + }, + "first_seen": 1731582585, + "birth_edge": null, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [ + "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij" + ] + }, + "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p": { + "address": "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 282 + }, + "first_seen": 1707956423, + "birth_edge": { + "sink": "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 92000000, + "signature": "2Zo3SqZ1zdcF99BygckTotHufbXupGv4FLFyEdVxbSwKedaeJRsp2gb8Uwjgjf8PkwQgsibXyJvpBZwFErrJ6FDc", + "slot": 248216739, + "block_time": 1707956423, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [] + }, + "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu": { + "address": "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1783033845, + "birth_edge": { + "sink": "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 1171377350, + "signature": "5T6rTfTX8JKsNiXf9PCnAEL4w7HLzJfsVBeQwsTRcsuK9TyxUV9ym5MPTpVBepapLkDMQeTPdAEigW14azAfdiT8", + "slot": 430402156, + "block_time": 1783033845, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [] + }, + "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2": { + "address": "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2014 + }, + "first_seen": 1728020846, + "birth_edge": { + "sink": "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 203950000, + "signature": "5U8Sgir6egiAF84ZZW13rDa1ek69yJpBcTmv227D88J6Gbm69bF6Dq7WKoLSdYEeaeXGPSCmN76jDFXzZEkeQh2Z", + "slot": 293595031, + "block_time": 1728020846, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE": { + "address": "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784933634, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA" + ] + }, + "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1": { + "address": "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7196 + }, + "first_seen": 1728755528, + "birth_edge": { + "sink": "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 839200000, + "signature": "2DdKCofnd53ZCijpwVNM4xRHmTXYT8YqeRh99etQB4J3YmX48b1sNn6vq2RGrqtmsgbKq4jH1jHc9DnoSctSurUw", + "slot": 295234119, + "block_time": 1728755528, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [] + }, + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG": { + "address": "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4316 + }, + "first_seen": 1743001899, + "birth_edge": { + "sink": "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "sources": [ + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "value": 200000000, + "signature": "3Rg9fS58hDfbMcnKWU6jeJXUva4g1jetwoNwvp7YiiH1GXDadsF7ZqxutWpxq6VXuHdRWpq61TFtQMs6KRhrkmqr", + "slot": 329323260, + "block_time": 1743001899, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "funded": [ + "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7" + ] + }, + "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9": { + "address": "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1773 + }, + "first_seen": 1699496374, + "birth_edge": { + "sink": "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 3442545653, + "signature": "Pvpzz5cpDfRekWPtKKux8YPWQuzJhsEonx1YpQLx9HkBiWkknk4Cwj6dyvof1ha8fnmrEkZqGAefpquu6G6HUDD", + "slot": 228889688, + "block_time": 1699496374, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [] + }, + "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2": { + "address": "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 163 + }, + "first_seen": 1770880341, + "birth_edge": { + "sink": "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1178878050, + "signature": "3Yn2VJBxAgpBmQ8XD5EXEPEdPfyYuvrRfiJEFCMn4JS3CwYURPu7QTwjsz9Masn96rwjNriemwBLyzFe8ZVzLwbq", + "slot": 399714845, + "block_time": 1770880341, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE": { + "address": "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784780643, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39" + ] + }, + "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA": { + "address": "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783307215, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq": { + "address": "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1705 + }, + "first_seen": 1777229947, + "birth_edge": { + "sink": "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq", + "sources": [ + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + ], + "value": 3615443530, + "signature": "ev9FqbVk8jCJiy27MypNycKFYYeRJJJCpH6WiNPM5buomXGKnTHqz9YL5heguK76dncGDd8KnEMvKruGMzQFDRc", + "slot": 415842709, + "block_time": 1777229947, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9WL2A89YBr6X47ABKYNzPentWiBA3H8tpaiuf5CaYHx6" + ], + "funded": [ + "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV" + ] + }, + "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs": { + "address": "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 302 + }, + "first_seen": 1774289101, + "birth_edge": { + "sink": "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "sources": [ + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS" + ], + "value": 1406603230, + "signature": "58WsdGGRCSKky5UXVe4QCaZpALbxjqfksCdHYZUj6HtNo5GmE3SwBi7tfun9dovyXN7CHVXt56c7wc28deCM4NKb", + "slot": 408377546, + "block_time": 1774289101, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS" + ], + "funded": [] + }, + "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq": { + "address": "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1758453103, + "birth_edge": { + "sink": "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "sources": [ + "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi" + ], + "value": 1111260006, + "signature": "2wqEciaCd8SdPS1BaEWBxZfVTrHk3sMS7PZB8LobFDQA7e49g7awaW44rGj2gRiWbCoU4rW4SWbn7qCpQtPXaetx", + "slot": 368284051, + "block_time": 1758453103, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i": { + "address": "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1753315217, + "birth_edge": null, + "fee_payers": [], + "funded": [] + }, + "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb": { + "address": "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 253 + }, + "first_seen": 1776516090, + "birth_edge": { + "sink": "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "sources": [ + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1" + ], + "value": 268000000, + "signature": "3KB7X9tCYoBd3HrsMzGTYQ1avb3ceSiXpHuQv6qQ9LPLKVnxVoUaXdBeKF5qLp71mMTNmHXsNcKe3Hbd4JYs29C8", + "slot": 414039567, + "block_time": 1776516090, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1" + ], + "funded": [] + }, + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z": { + "address": "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1230 + }, + "first_seen": 1769712791, + "birth_edge": { + "sink": "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z", + "sources": [ + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG" + ], + "value": 159000000, + "signature": "37ERW5HvvBbZ7xPSZK8kHSjpshWttD8PGTqwnw4hrmou8DyqToYBSU1oV1YqHfEdVbqBkkLULdg7VUmuZBsNkVZG", + "slot": 396754483, + "block_time": 1769712791, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG" + ], + "funded": [ + "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG" + ] + }, + "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf": { + "address": "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 180 + }, + "first_seen": 1745992468, + "birth_edge": null, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv": { + "address": "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 49 + }, + "first_seen": 1778543921, + "birth_edge": null, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx": { + "address": "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 639 + }, + "first_seen": 1681873953, + "birth_edge": null, + "fee_payers": [ + "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK" + ], + "funded": [] + }, + "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw": { + "address": "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq" + ] + }, + "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R": { + "address": "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9 + }, + "first_seen": 1784990514, + "birth_edge": { + "sink": "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 10091911, + "signature": "8noYJ5aCs9N6rbVBKCGMwVfVeCg1GQHGXhaNHP2rVcBZSMgbgiUpoNtpQ8XkASe78iZs3JqGk3c178jbeFgdrzc", + "slot": 435145890, + "block_time": 1784990514, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [] + }, + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS": { + "address": "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784778578, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ" + ] + }, + "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi": { + "address": "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq" + ] + }, + "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K": { + "address": "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 483 + }, + "first_seen": 1756671400, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4" + ], + "funded": [] + }, + "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw": { + "address": "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1783107306, + "birth_edge": { + "sink": "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 9000000, + "signature": "4B1byRx7pvEAt5NfXDUwEqo2TyMbm1qFV1Qakd6tnjmjfUHQ6ThQJFpcN129xXoS29GPPtvScVyHGBkCV6kd9s2m", + "slot": 430584264, + "block_time": 1783107306, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [] + }, + "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh": { + "address": "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 322 + }, + "first_seen": 1761374606, + "birth_edge": { + "sink": "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "sources": [ + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" + ], + "value": 25732740, + "signature": "3AhoNZZLvSQ5gNfWsTeRkjLi8W9t1P63EPXD3YKxErjnAP37U44njwCmpBaygX1xi5fgUNHHSZaeMiEJq9F2jrHi", + "slot": 375651141, + "block_time": 1761374606, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" + ], + "funded": [] + }, + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16": { + "address": "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1781755484, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq" + ] + }, + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V": { + "address": "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1582 + }, + "first_seen": 1700759478, + "birth_edge": { + "sink": "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "sources": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "value": 951634000, + "signature": "5vBY9n8k1qYPBcapzouiPJ9whnVFx18Y9Y574PJfDDwMNKt59QoCsGbRgke24y6T3zTyjNNRqEzCnJUAwfWGNeE7", + "slot": 231795042, + "block_time": 1700759478, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "funded": [ + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP" + ] + }, + "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n": { + "address": "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 135 + }, + "first_seen": 1780765625, + "birth_edge": { + "sink": "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "sources": [ + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP" + ], + "value": 271700000000, + "signature": "5R2ekhoYBR8fWYnM27srHHH24YNpF79Bu2uws2dh3otMMSt7Kku9tgV5NUx2yFN53YjfhZ8iDv6zNzFJpuoZgyg2", + "slot": 424718918, + "block_time": 1780765625, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP" + ], + "funded": [] + }, + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu": { + "address": "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784720381, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt" + ] + }, + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV": { + "address": "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784708330, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu" + ] + }, + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4": { + "address": "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1777641452, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm" + ] + }, + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ": { + "address": "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 21 + }, + "first_seen": 1709918366, + "birth_edge": { + "sink": "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 1696237745, + "signature": "qdFZbtFQnSrYx5CCo78yqoFY1ZYvhKgUL5gPeKiiG1LJ2TG2jC4yKtUdwidZiH5tZZ2bt4HEqU4pfjBbMeDYJNc", + "slot": 252918864, + "block_time": 1709918366, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [ + "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf" + ] + }, + "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28": { + "address": "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 98 + }, + "first_seen": 1780986875, + "birth_edge": { + "sink": "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "sources": [ + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c" + ], + "value": 30000000, + "signature": "5RkppwfwvNhhTswurZaM4gwPQiDaie82UhBdHt8mHJSES3ci8cmDuHHRLYXoFuWD7KdurykQXq7dUBfaBw8HAiqW", + "slot": 425274321, + "block_time": 1780986875, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c" + ], + "funded": [] + }, + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm": { + "address": "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1751863543, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK" + ] + }, + "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ": { + "address": "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 34 + }, + "first_seen": 1784808222, + "birth_edge": { + "sink": "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "sources": [ + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv" + ], + "value": 100000000, + "signature": "3XFtWHvCubsszFNB4U9XX592rAbYqdKaUjy7KPTjxKmcAUziERiSHsPLvbSozHGgAgiZjZmdStGTB99ASeYB8GPU", + "slot": 434711620, + "block_time": 1784808222, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv" + ], + "funded": [] + } + } +} \ No newline at end of file diff --git a/docs/PROVENANCE_METHOD.md b/docs/PROVENANCE_METHOD.md index c2b5574f..36cb56aa 100644 --- a/docs/PROVENANCE_METHOD.md +++ b/docs/PROVENANCE_METHOD.md @@ -283,6 +283,68 @@ Emit three values: A point estimate without this bracket is not publishable output. +### 2.6a Sampling error, which the bracket does not cover + +The bracket answers *what if the unresolved members had been something else*. It +says nothing about the other uncertainty, which is that the members measured are +a **draw** from a much larger population. Both are needed and neither substitutes +for the other: + +| | question | shape | +|---|---|---| +| bracket | what if the unresolved had landed differently? | exact bound, computed | +| bootstrap | how much of `ρ` is which depositors we drew? | percentile interval, resampled | + +**The estimator.** Resample the resolved members with replacement, `n` draws for +a sample of `n`, recompute `ρ` on the resampled class tally, repeat `B = 10,000` +times, and take the 2.5th and 97.5th percentiles. A class that no resampled +member landed in is **absent** from that replicate, not a class of size zero — +which is the whole reason the interval is asymmetric under a heavy tail: +resampling merges singletons, and fewer classes means higher `ρ`. + +**Reproducibility.** The generator is a splitmix64 implemented in-tree, seeded +from a published constant, because `analyze` is a pure offline pass whose output +a third party must be able to reproduce byte for byte. An external RNG makes that +promise depend on a dependency's version. + +#### The bias this does *not* remove, stated because it runs the wrong way for us + +Plug-in entropy — estimating `H(C)` by counting — is the maximum-likelihood +estimator, and it is **biased downward** when classes are many and members are +few. That is the regime every run in this project operates in. + +Since `ρ = 2^{−H(C)}`, understating `H(C)` means **overstating `ρ`**. So: + +> Every `ρ` published here is biased **high**. The true loss factor is plausibly +> smaller — the pools plausibly leak *less* than we report. + +A bootstrap resamples the same estimator, so its interval is centred on the +biased value: it measures the estimator's spread, not its distance from the +truth. Correcting this needs Miller–Madow or a coverage-adjusted estimator, and +neither is implemented here. + +It is stated this loudly for two reasons. First, it is the direction that makes +our own headline look worse rather than better, and a bias disclosed only when it +flatters is not a disclosure. Second, it is what makes **comparison** valid where +individual numbers are shaky: the bias falls in the same direction on every +population measured the same way, so a difference between two of them survives a +bias that neither absolute number does. + +#### Comparing two populations + +`ρ` is the headline because it is independent of `k` and therefore comparable +across pools. Exercising that means bootstrapping the **difference**, resampling +each population independently within a replicate, and reporting the interval on +`ρ(A) − ρ(B)`. + +**If that interval contains zero, the two populations are not distinguishable at +those sample sizes, and neither may be called the more concentrated.** Subtracting +two point estimates and reporting the sign is the error this exists to prevent: +two samples of the same underlying shape always differ by *something*. + +The unit test that matters here is not that a stark difference separates — that +is easy. It is that two identically shaped populations do **not**. + ### 2.7 Reference vectors for unit tests Exact values, computed at full double precision. Assert to 1e-9. From b79c4b27383c4011362ff0d0323c4348cbe7a923 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 02:10:10 -0300 Subject: [PATCH 42/83] docs: pre-register the two re-collections the tracer fix forces --- docs/MEASUREMENT_LOG.md | 46 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index 99e8bbd4..4655c49f 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -286,6 +286,52 @@ in Run 4 suggests a different population would have a lighter one. including a result that undercuts the argument for measuring privacy pools at all, and including a refusal. +## Runs 6 and 7 — pre-registered re-collections, forced by a bug in our own tracer + +Committed before either collection started. + +**Why these exist.** Run 5's control produced an unresolved bucket six times +larger than the privacy pool's, in the *evidence* category. Checking two of those +seeds by hand found the cause, and it was ours: the tracer implemented "the +oldest transaction, if it is a credit" while the documented rule — in the README, +in the method, and in the module's own comment — is **the oldest credit**. An +address whose first transaction merely references it, which is common, was +reported as having no funding at all. + +That is an infrastructure limit reported as evidence about the chain, which §6 of +the method forbids in as many words. It is fixed, with the exhausted case given +its own outcome so it counts as budget and never as evidence. + +**Every `ρ` published before this point was computed with the broken tracer**, +including the Run 4 headline. So both populations are collected again. + +| | frame | parameters | +|---|---|---| +| **Run 6** | the same 100 Privacy Cash depositors as Runs 2–4 | depth 16, page cap 24, birth scan 24 | +| **Run 7** | the same 100 Marinade depositors as Run 5 | depth 16, page cap 24, birth scan 24 | + +Identical parameters to Run 4 and Run 5 respectively, save the birth scan the fix +introduces. Same frames, not re-drawn. + +**Prediction, recorded in advance.** Resolution rises in both, and by more in the +control, because that is where the misdiagnosed bucket was concentrated. The +direction of the *comparison* is genuinely unknown, and that is the point of +running both: the bug suppressed resolution in whichever population had more +addresses whose first transaction was not their funding, and there is no reason +to assume that is the one that flatters us. + +`ρ` itself may move in either direction. Members who were previously dropped will +land in classes, and whether they land in the crowded ones or in new singletons +decides the sign. + +**Stopping rule.** One collection per frame at these parameters. Both are +reported whatever they say — including a corrected headline worse than the one +already published, and including the comparison failing to separate. + +**What is not being re-run.** Runs 1 and 2, which produced no headline: one on a +size-biased frame, one above the RPC-failure limit. Neither's conclusion depends +on the birth-edge rule, and both remain in this document as they were. + ## What we do not conclude **Nothing about how private that pool is, and the headline does not become that From 011a94e4fd7bc6535eafac09950d24e16ee52e37 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 02:16:54 -0300 Subject: [PATCH 43/83] docs: record the birth-edge bug in the method, beside everyone else's --- README.md | 8 ++++++++ docs/PROVENANCE_METHOD.md | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8543e3a0..c99f0ae7 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,14 @@ weakest of them, and none of them is visible in the output. wallet's history. Reading the most recent transactions instead is the wrong end of the record for any wallet with more than a handful, and manufactures "unresolved" for precisely the active wallets worth resolving. +- **The oldest transaction is where that search starts, not where it ends.** An + address often appears in someone else's transaction — an ATA creation, a + multisig setup — before it is ever funded, so its oldest transaction credits it + nothing. We got this wrong: the tracer read that one transaction and reported + "no incoming edge", turning *we stopped reading* into *there is nothing there* + for wallets with eleven thousand transactions. Found while building a control + population, fixed, every affected number re-collected, and written up in + `docs/MEASUREMENT_LOG.md` rather than quietly repaired. - **The hub threshold is decoupled from the paging cap.** Collapse them into one number — easy to do, since both are "how many signatures do we look at" — and "reaches an attributable origin" quietly becomes a synonym for "hit the RPC page diff --git a/docs/PROVENANCE_METHOD.md b/docs/PROVENANCE_METHOD.md index 36cb56aa..065a4d31 100644 --- a/docs/PROVENANCE_METHOD.md +++ b/docs/PROVENANCE_METHOD.md @@ -581,7 +581,42 @@ clean ("who created this wallet"), and one RPC call for most addresses. `getSignaturesForAddress` returns **newest-first**, and the only cursors are `before` (page backwards) and `until` (stop early); there is no forward cursor and `limit` caps at 1,000 **[V]**. So for an address with fewer than 1,000 lifetime signatures, the -**last element of the first page** is the birth transaction. +**last element of the first page** is the oldest transaction. + +**The oldest transaction is not the birth edge.** It is where the search for the birth +edge starts. An address routinely appears as a passive participant in someone else's +transaction — an associated-token-account creation, a multisig configuration, a program +call that merely references it — *before* it ever receives value, and in that case its +oldest transaction carries a lamport delta of exactly zero for it. + +So the walk continues **forward** from the oldest until a crediting transaction appears, +bounded by `BIRTH_SCAN_CAP`. Exhausting that budget without finding a credit is +`birth-scan-exhausted`, which is **budget-unresolved**, and it is a separate outcome +from `no-incoming-edge` for a reason that is the whole discipline of §6 in miniature: + +| outcome | claim | about | +|---|---|---| +| `no-incoming-edge` | this address has no funding credit | the chain | +| `birth-scan-exhausted` | we stopped reading before finding one | us | + +> **Failure mode, and this one was ours.** The first implementation here fetched *only* +> the oldest transaction and, if it was not a credit, reported `no-incoming-edge`. Two +> seeds checked by hand had 6,457 and 11,491 lifetime signatures, were plainly funded, +> and were reported as having no funding at all — because one transaction out of eleven +> thousand had a zero delta. The measured cost was a **six-fold** inflation of the +> evidence-unresolved bucket in the population where it bit hardest, and the reason it +> survived review is that the output was indistinguishable from a real finding. +> +> It is recorded here rather than quietly repaired because a document that lists other +> people's failure modes and omits its own is not a method, it is marketing. + +> **Failure mode.** Scanning an address's most recent transactions to find its funder. +> `getSignaturesForAddress` returns newest-first, so taking the first handful is the +> path of least resistance — and funding is by definition among an address's *oldest* +> transactions. For any address with more history than the scan window, this reads the +> wrong end of the record, and it fails **silently**: the output is "unresolved", which +> is indistinguishable from a genuinely unresolvable wallet. The bias falls hardest on +> active wallets, which are the ones a provenance study most needs to resolve. > **Failure mode.** Scanning an address's most recent transactions to find its funder. > `getSignaturesForAddress` returns newest-first, so taking the first handful is the From 810fef48ad8c35387e15e492685f5591d62c3f33 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 02:20:08 -0300 Subject: [PATCH 44/83] measure: corrected headline from the re-collected privacy pool, rho = 0.0955 --- README.md | 30 +- data/sample-privacycash-run6.json | 5980 +++++++++++++++++++++++++++++ docs/MEASUREMENT_LOG.md | 46 + 3 files changed, 6043 insertions(+), 13 deletions(-) create mode 100644 data/sample-privacycash-run6.json diff --git a/README.md b/README.md index c99f0ae7..f794fadd 100644 --- a/README.md +++ b/README.md @@ -41,10 +41,12 @@ So this submission claims exactly two things: member. 2. **The membership side is measured**, from real mainnet data, with the method and its limits published beside the number. Measured on a live pool's - depositors: **`ρ = 0.1032`**, inside an unresolved bracket of - `0.0316 … 0.1318`, from a clean census with zero infrastructure failures — - 50 of 84 members reached a provenance class. Knowing a member's funding class - costs that pool roughly an order of magnitude of its nominal anonymity. + depositors: **`ρ = 0.0955`**, inside an unresolved bracket of + `0.0350 … 0.1136` and a 95% sampling interval of `0.0848 … 0.1790`, from a + clean census with **zero** infrastructure failures and **zero** members whose + funding we claim not to exist — 54 of 83 reached a provenance class. Knowing a + member's funding class costs that pool roughly an order of magnitude of its + nominal anonymity. The sample's class distribution is heavy-tailed and two-thirds unobserved, and that is reported as a finding rather than hidden as a caveat: raising the @@ -176,20 +178,22 @@ mirror analyze --sample sample.json # pure, offline, deterministic mirror compare --sample a.json --against b.json # is the difference real? ``` -`data/sample-privacycash-run4.json` is the committed artifact behind the -headline, and `data/sample-privacycash.json` is Run 3, the earlier and smaller -budget it is compared against. Both are committed, so anyone holding them -recomputes both numbers without RPC access and without trusting that our -endpoint behaved the same way on their machine — including the comparison that -shows coverage *falling* as resolution rose. +`data/sample-privacycash-run6.json` is the committed artifact behind the +headline. Every earlier sample stays committed beside it — Run 3, Run 4, and the +control — so a reader can recompute not only the number but the corrections: +the budget increase between Runs 3 and 4, and the tracer fix between Runs 4 and +6. None of it needs RPC access or trust in how our endpoint behaved. `docs/MEASUREMENT_LOG.md` records every run, including the two that produced no headline at all: one whose frame was size-biased and resolved nothing, and one that came back above the 1% RPC-failure limit. Those two have no committed sample, and saying so is part of the record — what is committed is what the -published numbers come from. Run 4's budget and its predictions were committed -to git *before* the collection started, so its parameters are a declaration -rather than a description. +published numbers come from. + +**Every run since Run 4 had its budget and its prediction committed to git +before the collection started**, so the parameters are declarations rather than +descriptions, and the predictions are on the record including the ones that +turned out wrong. ### Design choices, and the failure each one exists to avoid diff --git a/data/sample-privacycash-run6.json b/data/sample-privacycash-run6.json new file mode 100644 index 00000000..102fb96c --- /dev/null +++ b/data/sample-privacycash-run6.json @@ -0,0 +1,5980 @@ +{ + "manifest": { + "endpoint": "https://mainnet.helius-rpc.com", + "first_available_block": 0, + "archival_probe_ok": true, + "collected_at": 1785042653, + "config": { + "scope": "Sol", + "depth_max": 16, + "min_edge_lamports": 500000, + "sig_page_cap": 24, + "page_size": 1000, + "birth_scan_cap": 24 + }, + "thresholds_hub_signatures": 5000, + "thresholds_distributor_fanout": 20, + "thresholds_cluster_min_size": 3, + "rpc_calls": 1776, + "excluded_non_wallet": [ + "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp", + "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", + "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB", + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", + "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY", + "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv", + "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis", + "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX", + "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", + "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG", + "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS", + "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA", + "2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha", + "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp", + "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH" + ], + "ambiguous_attribution_rate": 0.18902439024390244 + }, + "chains": [ + { + "seed": "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "visited": [ + "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "visited": [ + "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k", + "visited": [ + "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k", + "9HGyyJnWK4b67E97bB4SXpjbxpYmTBMaKNmWmfmYe4DK", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", + "visited": [ + "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E" + ], + "stop": "PageCapHit" + }, + { + "seed": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "visited": [ + "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x", + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr", + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ", + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP", + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho", + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva", + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE", + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g", + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99", + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT", + "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD" + ], + "stop": "PageCapHit" + }, + { + "seed": "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "visited": [ + "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "visited": [ + "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" + ], + "stop": "PageCapHit" + }, + { + "seed": "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff", + "visited": [ + "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff" + ], + "stop": "PageCapHit" + }, + { + "seed": "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "visited": [ + "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "visited": [ + "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v", + "visited": [ + "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v", + "7xcCLxFxNxGdj8XPTveMh7gZ8vhEowe6Si9QpoFuCZai", + "C8nKoCYa6vbtv51Gszak8W8UoMzCu32ks56rDYdMpwwr", + "FzZ77TM8Ekcb6gyWPmcT9upWkAZKZc5xrYfuFu7pifPn" + ], + "stop": "PageCapHit" + }, + { + "seed": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "visited": [ + "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd", + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC", + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7", + "7h3fgsJgJGXkUtqvtApMJbvJugMfzS4yz9wYoqsMqjjs", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "visited": [ + "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" + ], + "stop": "PageCapHit" + }, + { + "seed": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "visited": [ + "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf", + "visited": [ + "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf" + ], + "stop": "PageCapHit" + }, + { + "seed": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", + "visited": [ + "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx", + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K", + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr" + ], + "stop": "PageCapHit" + }, + { + "seed": "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "visited": [ + "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT", + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1", + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "visited": [ + "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "visited": [ + "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "visited": [ + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165" + ], + "stop": "PageCapHit" + }, + { + "seed": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "visited": [ + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", + "visited": [ + "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx" + ], + "stop": "PageCapHit" + }, + { + "seed": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "visited": [ + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" + ], + "stop": "PageCapHit" + }, + { + "seed": "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "visited": [ + "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB" + ], + "stop": "LocalTerminal" + }, + { + "seed": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", + "visited": [ + "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "visited": [ + "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "visited": [ + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "visited": [ + "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "PageCapHit" + }, + { + "seed": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "visited": [ + "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx", + "visited": [ + "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx", + "DkzL477CEJQSynqqqStK378vfCjzxRMvotCz8xdoVzv9", + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "stop": "PageCapHit" + }, + { + "seed": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "visited": [ + "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV" + ], + "stop": "LocalTerminal" + }, + { + "seed": "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "visited": [ + "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "visited": [ + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN", + "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "visited": [ + "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "visited": [ + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", + "visited": [ + "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU", + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi" + ], + "stop": "PageCapHit" + }, + { + "seed": "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "visited": [ + "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "visited": [ + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "stop": "PageCapHit" + }, + { + "seed": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "visited": [ + "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "visited": [ + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U", + "visited": [ + "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U" + ], + "stop": "PageCapHit" + }, + { + "seed": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "visited": [ + "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ" + ], + "stop": "LocalTerminal" + }, + { + "seed": "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "visited": [ + "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "visited": [ + "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q", + "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r", + "FWKrAi8ZDaeKx9NQoJmLGyMoujLUUjsM8pqmghFqrtaf", + "fkY8mx6y5VYYsybgy3q2A5A8ZphJewgmBBhx4ZsEAQf", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "visited": [ + "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH", + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "stop": "PageCapHit" + }, + { + "seed": "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", + "visited": [ + "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT", + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "visited": [ + "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM", + "6n5o3qNBAjqVjSGZ8qDWoWAHZdxWKpENVh4sJMLzmwL9", + "6ZPorXXoNedd3u2unPzZRg18LDrE4CQZcUarumuhZdbD" + ], + "stop": "LocalTerminal" + }, + { + "seed": "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "visited": [ + "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "visited": [ + "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + ], + "stop": "PageCapHit" + }, + { + "seed": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "visited": [ + "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF", + "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP", + "9Kjd4PMdZFQrzuGvv7PuMA1uXTu433GP5CqHGNaJhtja", + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "stop": "PageCapHit" + }, + { + "seed": "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", + "visited": [ + "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "visited": [ + "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "visited": [ + "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr" + ], + "stop": "PageCapHit" + }, + { + "seed": "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "visited": [ + "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" + ], + "stop": "PageCapHit" + }, + { + "seed": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "visited": [ + "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" + ], + "stop": "PageCapHit" + }, + { + "seed": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "visited": [ + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "stop": "PageCapHit" + }, + { + "seed": "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "visited": [ + "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "visited": [ + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "visited": [ + "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe", + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor", + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu", + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S", + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "visited": [ + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "visited": [ + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G" + ], + "stop": "PageCapHit" + }, + { + "seed": "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "visited": [ + "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o" + ], + "stop": "LocalTerminal" + }, + { + "seed": "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "visited": [ + "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "visited": [ + "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob" + ], + "stop": "BirthScanExhausted" + }, + { + "seed": "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "visited": [ + "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "visited": [ + "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "visited": [ + "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "stop": "PageCapHit" + }, + { + "seed": "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "visited": [ + "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "visited": [ + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "visited": [ + "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB", + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx", + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S", + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr", + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh", + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm", + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94", + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf", + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ", + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x", + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha", + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t", + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe", + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH", + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz", + "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ" + ], + "stop": "DepthExceeded" + }, + { + "seed": "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "visited": [ + "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "visited": [ + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "visited": [ + "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B", + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "stop": "PageCapHit" + }, + { + "seed": "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR", + "visited": [ + "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR" + ], + "stop": "PageCapHit" + }, + { + "seed": "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "visited": [ + "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn" + ], + "stop": "PageCapHit" + }, + { + "seed": "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "visited": [ + "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "visited": [ + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "visited": [ + "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "PageCapHit" + }, + { + "seed": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "visited": [ + "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "visited": [ + "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "stop": "PageCapHit" + }, + { + "seed": "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "visited": [ + "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" + ], + "stop": "PageCapHit" + }, + { + "seed": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "visited": [ + "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" + ], + "stop": "PageCapHit" + }, + { + "seed": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", + "visited": [ + "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM" + ], + "stop": "PageCapHit" + } + ], + "facts": { + "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E": { + "address": "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1784615427, + "birth_edge": { + "sink": "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 110900000, + "signature": "5e6e31Ja1kF2DbV7qrcSW9RZYywU7TpJfxr667CtkwBe4toXGeyZnYtdT8NJeQzM1fTpQULLkDXkraeVcYxArg8W", + "slot": 434252353, + "block_time": 1784615427, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma": { + "address": "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 607 + }, + "first_seen": 1783375005, + "birth_edge": { + "sink": "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 22913500000, + "signature": "2AkoeVHhVQwaNuxQEya9FmdWcho8YDPBUExnQwQ51LZCGAN81Xa6gmUiq5KufZD7VNapVbL566cGNQ54cHcQGDAZ", + "slot": 431247217, + "block_time": 1783375005, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z": { + "address": "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4277 + }, + "first_seen": 1647049347, + "birth_edge": { + "sink": "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z", + "sources": [ + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" + ], + "value": 7244953950, + "signature": "419TeAWehDaugVDXtfT9GochWU2uzZfYE8Mj9KcRLxDtwZpVies6nT8ujSsqz5xcah5gEALomEeyeL5igrt1ZFeG", + "slot": 124535935, + "block_time": 1647049347, + "ambiguous_attribution": false + }, + "fee_payers": [ + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs" + ], + "funded": [ + "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H" + ], + "birth_scan_exhausted": false + }, + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm": { + "address": "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784761134, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH" + ], + "birth_scan_exhausted": false + }, + "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ": { + "address": "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 38 + }, + "first_seen": 1783722353, + "birth_edge": { + "sink": "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 12948500000, + "signature": "5rMkvoDiSrN16YHjCLekrebvkmsHxvssazWRbP2Fq5rZNhyyMcUTULMb136XBMrKVZzwRyhbj7qb3cx3sgGiWAoX", + "slot": 432098751, + "block_time": 1783722353, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha": { + "address": "2Ej2hAgZihAPV4JKYEXMGYwL1TvSaPQDS8KNGr9B22ha", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9": { + "address": "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784475307, + "birth_edge": { + "sink": "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9", + "sources": [ + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn", + "6tckHFBpiJ8YgYN8FUskvtvTpXQZ55g5LHeo1kvELoDQ" + ], + "value": 5010000000, + "signature": "4bs1AqpjFmhSM6Hgg7nGwN7FbytYnLRcGmiTMP6TCCRP2rhhT4hf8Bb62Jm7uTqxc4vs9scPgT3vbEb98uQfdNF3", + "slot": 433922248, + "block_time": 1784475307, + "ambiguous_attribution": true + }, + "fee_payers": [ + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X": { + "address": "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1743262736, + "birth_edge": { + "sink": "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X", + "sources": [ + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" + ], + "value": 779470319, + "signature": "2YjKzdqoKhck9hGc8VmXR5gxdnX1SPDG6WByy1Mm3tHz5rzESsiEf3TLLof8but3XH3XqMVX9ZHbvAR9fhVPanWM", + "slot": 329976683, + "block_time": 1743262736, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF" + ], + "funded": [ + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu" + ], + "birth_scan_exhausted": false + }, + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V": { + "address": "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", + "owner": null, + "executable": false, + "signatures": { + "Exact": 249 + }, + "first_seen": 1776070891, + "birth_edge": { + "sink": "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V", + "sources": [ + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ" + ], + "value": 600000000, + "signature": "2ifPXJE5e8zqjQTqTP7duLXvDyDjTsJrqcf5iEdywn3Z29p3Zsr3RcgYFXPkkxZMhMNg3HQyeVHSqQHhCrDAYnPM", + "slot": 412912638, + "block_time": 1776070891, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ" + ], + "funded": [ + "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU" + ], + "birth_scan_exhausted": false + }, + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH": { + "address": "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894159, + "birth_edge": { + "sink": "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH", + "sources": [ + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz" + ], + "value": 849144919, + "signature": "4YP76aBoEjaNaxf1e6Va5x4ZZLNVfo6vNpY8kzqQcquQZWaGeiiJgrKaPTrP1udmGV8Kj82jNeBCsH8PzcwBPk9b", + "slot": 394707612, + "block_time": 1768894159, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz" + ], + "funded": [ + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe" + ], + "birth_scan_exhausted": false + }, + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy": { + "address": "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1600 + }, + "first_seen": 1784752160, + "birth_edge": { + "sink": "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy", + "sources": [ + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" + ], + "value": 43881516584, + "signature": "3NEwDfuC73NVthq8zeasPw9Ud6qcWZiy65wFFZSWcbxP86UjAsc9ZPApAdjk7EYo88ptZcUZbSLdmQwZkgqSCcRc", + "slot": 434577818, + "block_time": 1784752160, + "ambiguous_attribution": false + }, + "fee_payers": [ + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" + ], + "funded": [ + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x" + ], + "birth_scan_exhausted": false + }, + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg": { + "address": "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12 + }, + "first_seen": 1781285770, + "birth_edge": { + "sink": "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg", + "sources": [ + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G" + ], + "value": 1677937000, + "signature": "2TJSuukc2SNr9YL2qRHeNHmu37kfsE2cBnQkz7TJTxuRkVw48fefVYqxsGCV2ULsb1PnVvzmcQGJt5J7uouabgYZ", + "slot": 426027687, + "block_time": 1781285770, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem": { + "address": "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", + "owner": null, + "executable": false, + "signatures": { + "Exact": 263 + }, + "first_seen": 1713736580, + "birth_edge": { + "sink": "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem", + "sources": [ + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" + ], + "value": 12006763000, + "signature": "62g49CyyWTmom7Qxdnr64iSy7W8PDuxrZojz5q6ztpgYkM7WwwXkfcyeNVditM45iomtRpgqXUbLW8kXpScStkuL", + "slot": 261492401, + "block_time": 1713736580, + "ambiguous_attribution": false + }, + "fee_payers": [ + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE" + ], + "funded": [ + "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq" + ], + "birth_scan_exhausted": false + }, + "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf": { + "address": "2kpF5eiiZjPh3dySyb4eFHg2FFDDyxBAKtf8kox681Lf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1768518508, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz": { + "address": "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1771863926, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38" + ], + "birth_scan_exhausted": false + }, + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S": { + "address": "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1655911154, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J" + ], + "birth_scan_exhausted": false + }, + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS": { + "address": "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783794615, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B" + ], + "birth_scan_exhausted": false + }, + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1": { + "address": "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 100 + }, + "first_seen": 1784818359, + "birth_edge": { + "sink": "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1", + "sources": [ + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7" + ], + "value": 6000000000, + "signature": "2StbhyBVwhP2wRhTYMM31x5qU3B2WBr1jFXx6KD6CGeXp9xpWQqN7RKoxa98X86h8v6SGprqMdi9CVGh9PLSjAQH", + "slot": 434735789, + "block_time": 1784818359, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7" + ], + "funded": [ + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT" + ], + "birth_scan_exhausted": false + }, + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr": { + "address": "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1784752151, + "birth_edge": { + "sink": "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr", + "sources": [ + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ" + ], + "value": 45278992944, + "signature": "LnCoQVyRCZyi9ktCp3JyZPKhGrTu19FzpmhP7SjvQ8TKYS7uu9dPE23AuzRQz32YWqaTQG9rabCkoAihw2swguS", + "slot": 434577797, + "block_time": 1784752151, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ" + ], + "funded": [ + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" + ], + "birth_scan_exhausted": false + }, + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT": { + "address": "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1332 + }, + "first_seen": 1632786500, + "birth_edge": { + "sink": "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT", + "sources": [ + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m" + ], + "value": 100000000, + "signature": "2BLRDfM37WhxjZUNXSnPPqSZP3Yq2xTG2BsyUiQWCFaLcqPigMGHgSrX2uijsXVbB9AKL4sYZenPjui5ArFMGjcP", + "slot": 98678981, + "block_time": 1632786500, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m" + ], + "funded": [ + "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t" + ], + "birth_scan_exhausted": false + }, + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt": { + "address": "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784444104, + "birth_edge": { + "sink": "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt", + "sources": [ + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" + ], + "value": 5986856990, + "signature": "3k3C9SKrmZZ9aKwV75WzsqDnXRj1jnrfVoq6W5E4wxyjcAH7ZGiMbrDKVqStRdtpFm4Y2vzYA8WvEyUdSs67b1UN", + "slot": 433847718, + "block_time": 1784444104, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf" + ], + "funded": [ + "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH" + ], + "birth_scan_exhausted": false + }, + "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL": { + "address": "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784544632, + "birth_edge": { + "sink": "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL", + "sources": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "value": 928815670, + "signature": "85PNojTrpgdkLHWP1CQRGD6G6CttqKUCr5zYDjLXkFsB4teH5D9kcZqZjTAWX3ydRqfVhwLAKLzC6zYsU3K1A8L", + "slot": 434085334, + "block_time": 1784544632, + "ambiguous_attribution": false + }, + "fee_payers": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe": { + "address": "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894162, + "birth_edge": { + "sink": "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe", + "sources": [ + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH" + ], + "value": 849139919, + "signature": "2R1Goqyiuu2pZC2cXWBUawgSdAFH38yF9ciQW9p9igR6YUwHoKq9iigoHZeQgBq4ZrcmdMkp39jThvqv2bZcevMF", + "slot": 394707621, + "block_time": 1768894162, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH" + ], + "funded": [ + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t" + ], + "birth_scan_exhausted": false + }, + "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H": { + "address": "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 528 + }, + "first_seen": 1734052375, + "birth_edge": { + "sink": "3MaDFqYf1M3296QvgBhYFZw7FhRd48mJP91jePnv8e8H", + "sources": [ + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z" + ], + "value": 1020000000, + "signature": "4Mm83Hp6ciDRNzM3tSFJt6AyoXPc7kD4moyqFobEtqzXDAnC2k5hNt83wNGSsn61KQjaNdyYYqWHcJjaUA9qubwX", + "slot": 307120184, + "block_time": 1734052375, + "ambiguous_attribution": false + }, + "fee_payers": [ + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH": { + "address": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 277 + }, + "first_seen": 1784444332, + "birth_edge": { + "sink": "3THAf1scfXLY7JjNgqx38pUTkGjmGe1KEJNR7PBTHvpH", + "sources": [ + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt" + ], + "value": 2985933011, + "signature": "4e1bYdz4z6QTzWFbSbjFRJpDdhuc2m2zaopi6x8N1w81fa6352rDi2VCzqyxKdRuvPKthVeikZXGj91o6bvCfeK4", + "slot": 433848260, + "block_time": 1784444332, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr": { + "address": "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1780923034, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh" + ], + "birth_scan_exhausted": false + }, + "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7": { + "address": "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784462394, + "birth_edge": { + "sink": "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 90000000, + "signature": "4jhAt2GJwyGRfe5sJkFB2HVjnUzLi86PVfucCvUM6u3JFRwegv57f4ZkhmRH6huyS8pNEZa8nbPs9Kb6PtXYUAqz", + "slot": 433891372, + "block_time": 1784462394, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99": { + "address": "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99", + "owner": null, + "executable": false, + "signatures": { + "Exact": 662 + }, + "first_seen": 1783864275, + "birth_edge": { + "sink": "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99", + "sources": [ + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT" + ], + "value": 16330335554, + "signature": "32dUmLfqYokcYLdCcE19JzJ8Y423ssL8GJ6qLEDaZtmWHsQtpTF4e9KVxmSuidihQAcV5cuPpsMZjhWWd8F149TB", + "slot": 432444959, + "block_time": 1783864275, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT" + ], + "funded": [ + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g" + ], + "birth_scan_exhausted": false + }, + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu": { + "address": "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1764 + }, + "first_seen": 1781821319, + "birth_edge": { + "sink": "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu", + "sources": [ + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ" + ], + "value": 400000000, + "signature": "t439hwXMQP6B4Yk83sUHquHMZR9q8bqQK4na6cWaxbvVCnQKw5hzceNSBXCJKRmcEzmJBFLd19bfcWsSDKGxhU6", + "slot": 427377732, + "block_time": 1781821319, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ" + ], + "funded": [ + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor" + ], + "birth_scan_exhausted": false + }, + "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv": { + "address": "3kyvLN87RQhv4Gy1AyQgXTxGxa8pr448CiaGtPaoKzGv", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze": { + "address": "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 704 + }, + "first_seen": 1784674265, + "birth_edge": { + "sink": "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 6040700317, + "signature": "3tGju22hVCi5Tn5mQv65Uokr3ahGwzYsU5urYkse9Bq4SgSRPmwiU8FzhX51HBdMCKyTor6i8G1uc2LnhHge4uRb", + "slot": 434392522, + "block_time": 1784674265, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9": { + "address": "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", + "owner": null, + "executable": false, + "signatures": { + "Exact": 42 + }, + "first_seen": 1682118364, + "birth_edge": { + "sink": "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9", + "sources": [ + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" + ], + "value": 11742708040, + "signature": "4fDKVyLL5y4Kzuedc8MGwhkkXd1oWbkMqBZTsHtc1H7NFTWijug7Lciv42HG6VkxfjZSemzGn6fCysTTxQPG2Zsb", + "slot": 189742975, + "block_time": 1682118364, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" + ], + "funded": [ + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" + ], + "birth_scan_exhausted": false + }, + "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ": { + "address": "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894142, + "birth_edge": { + "sink": "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ", + "sources": [ + "3LKFtdjyaX773fYgnXShWhicr3ZcNaSWWz2ggQVBgtsC" + ], + "value": 849154919, + "signature": "3fTk4eCJFMtoe3jFJ4XhXYEHqurQ5DHnehPcQoWMQLXmPmQTdns183yrWX7C8YWCvrtV7GPaU1GJ7WZfpdB9KSo1", + "slot": 394707570, + "block_time": 1768894142, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3LKFtdjyaX773fYgnXShWhicr3ZcNaSWWz2ggQVBgtsC" + ], + "funded": [ + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz" + ], + "birth_scan_exhausted": false + }, + "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz": { + "address": "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894156, + "birth_edge": { + "sink": "4UEaVZfqH92tZheffRYcoWs2kVqDSe4t34PeYQXsLWYz", + "sources": [ + "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ" + ], + "value": 849149919, + "signature": "ebF99xjMkDDNHUYqxCU6fukrRihQpvEHCgpii9dnf6AeBFGPYvk94S2cq7QnfBfhRooK1361Xddiaksk6hnVxQX", + "slot": 394707604, + "block_time": 1768894156, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4G4eUzBDGsnhubwxtB9CYK4k1t9xxs436f8mArvaMfHQ" + ], + "funded": [ + "2QfFzSwpYUMFmgwjJnDQwsTHXyCoEEZy7Jt3uybZ4DXH" + ], + "birth_scan_exhausted": false + }, + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr": { + "address": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9790 + }, + "first_seen": 1781265894, + "birth_edge": { + "sink": "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 209692102518, + "signature": "2WnT866FtZdcXqRvfQoypXGhWSQRtqa4cCg453yjkGSuZA9fs8LJviugPbbGP9GYu3TDJhohtJeDwq1AuoeqJBFi", + "slot": 425977688, + "block_time": 1781265894, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16": { + "address": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9762 + }, + "first_seen": 1772531584, + "birth_edge": { + "sink": "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16", + "sources": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "value": 302066170, + "signature": "5A3SzsgcHRLJQUVBqDNdCW7M8XnW6jS7TBjiuERozS7a5YAXc4EieN5d58j5Ts7mqNssUsZZj3TKUTNAett3gqgK", + "slot": 403914286, + "block_time": 1772531584, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT": { + "address": "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT", + "owner": null, + "executable": false, + "signatures": { + "Exact": 273 + }, + "first_seen": 1782950807, + "birth_edge": { + "sink": "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT", + "sources": [ + "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD" + ], + "value": 100000000, + "signature": "5xcQPdqkxyx3pQsyF85wNxj7b4TuW3nB3wqryzTVHLtMeDfKsGzjY64jXDCnnfvru4BqEunbdYQzBAHBBJKG3d6V", + "slot": 430196288, + "block_time": 1782950807, + "ambiguous_attribution": false + }, + "fee_payers": [ + "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD" + ], + "funded": [ + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99" + ], + "birth_scan_exhausted": false + }, + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w": { + "address": "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1783188037, + "birth_edge": { + "sink": "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 100528380, + "signature": "5CdkjWty9v98DDGJPREJK7UpeGUaaBdQxo87kDxyqajMtFtbnzVuL2h9UCu4Ue4iVqu4vWgiBLbs8XCjnAswLrDR", + "slot": 430784309, + "block_time": 1783188037, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [ + "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k" + ], + "birth_scan_exhausted": false + }, + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk": { + "address": "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1013 + }, + "first_seen": 1781819169, + "birth_edge": { + "sink": "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 25895028000, + "signature": "5e4gkh2xn451jv5rnKUJN81VDUN9bJTY3VESq2eQ7ACZJ51DuXZMFBaat5X5tVe5VBLzSnGbKri4KnpFezdqG3mu", + "slot": 427372317, + "block_time": 1781819169, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S" + ], + "birth_scan_exhausted": false + }, + "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd": { + "address": "4vWvjT8n1tap7RPESBKq6qDyr6s1nrVK4fd8ERJWwGXd", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs": { + "address": "57vSaRTqN9iXaemgh4AoDsZ63mcaoshfMK8NP3Z5QNbs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1663976471, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "22spwq4CPtszGu3Ct2oakPbAiXSXB548ZQqtc78mMy1z" + ], + "birth_scan_exhausted": false + }, + "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ": { + "address": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 17 + }, + "first_seen": 1784823865, + "birth_edge": { + "sink": "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ", + "sources": [ + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" + ], + "value": 26325656111, + "signature": "4Fvr5kGDCyhHU3xmpCJvMRy4YAGLs93UAu2mbCG3V39pu9ASyDAqEeMqve3CmwgDUieLGp4q6uoGvFHWBmqt2hvK", + "slot": 434748892, + "block_time": 1784823865, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr": { + "address": "5GPq99e8UGo5SCrXXCrzA7HtZwGeQFcCWP64H4uWCRDr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr": { + "address": "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894220, + "birth_edge": { + "sink": "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr", + "sources": [ + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh" + ], + "value": 849094919, + "signature": "4MJQDBRmFT2jFhKZx7rY84rz4GThjUhU2twqk6URjTrCx1k6SQwZd4toBBR6bKnqS7GvPowPP17GnwJw8DGFqviH", + "slot": 394707768, + "block_time": 1768894220, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh" + ], + "funded": [ + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S" + ], + "birth_scan_exhausted": false + }, + "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS": { + "address": "5PrefjTSrQGrojdYWsbcb1HwsRt7unDS9dDD8qfH64vS", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t": { + "address": "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9235 + }, + "first_seen": 1638557855, + "birth_edge": { + "sink": "5R2ry1tX8FTM27u5ChahmpHadK1pUd8KsGmPd19scR2t", + "sources": [ + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT" + ], + "value": 1500000000, + "signature": "54yv3HKUtdYAymSqK7g6Hv3Q3CX9ugxzGJ2SZSF2Mv5oq28tBAwGVdKC6d9NXfbfcxSdkxjaUWson8cdW2Hw2ruH", + "slot": 109908841, + "block_time": 1638557855, + "ambiguous_attribution": false + }, + "fee_payers": [ + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV": { + "address": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5586 + }, + "first_seen": 1774645255, + "birth_edge": { + "sink": "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV", + "sources": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "value": 500000000, + "signature": "3wVtJf5fiq7g5cSb8ynKPJswdRx5RQ7GV1oPF1BMiFYKpAkf6T4ZdEbrnfEMQR3DLusyex4JCeZ8aSr9xxYkiuuq", + "slot": 409285335, + "block_time": 1774645255, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za": { + "address": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 842 + }, + "first_seen": 1784934334, + "birth_edge": { + "sink": "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za", + "sources": [ + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x" + ], + "value": 52977372793, + "signature": "4JY37vAAtVC9a5sQn4z8CY5mRJ5t8njqiBziNK9Ps8QtpG9m1qPsfCLFFo8HuGFjdPNTUtDaLQ9v2TF1auyUDWg7", + "slot": 435012131, + "block_time": 1784934334, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p": { + "address": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1783475831, + "birth_edge": { + "sink": "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p", + "sources": [ + "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ" + ], + "value": 388393719000, + "signature": "5imCWUcQWCKj7vDdLUbDXDeE567zN2Eg4Le8AWi6v7f1ZnsbtijSC6i65rA6BpVCsK9kc9fRrBwF4jGBARwWPf1m", + "slot": 431495612, + "block_time": 1783475831, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BrGihgGjCmpu2p96rou6GQj3sQpwgeud937jf4RvZP9G" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3": { + "address": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 14 + }, + "first_seen": 1784433292, + "birth_edge": { + "sink": "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 669228400, + "signature": "66Tc5f3Uko3nDvDySXyoV4rhTBSka9cPczYiReZ8upWdrXKNhRGUHTTsZizm9mdNDEYdTkUmfuynxX3QEPjthemd", + "slot": 433821901, + "block_time": 1784433292, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs": { + "address": "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783956150, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk" + ], + "birth_scan_exhausted": false + }, + "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV": { + "address": "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u" + ], + "birth_scan_exhausted": false + }, + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9": { + "address": "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784935158, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "7h3fgsJgJGXkUtqvtApMJbvJugMfzS4yz9wYoqsMqjjs", + "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "fkY8mx6y5VYYsybgy3q2A5A8ZphJewgmBBhx4ZsEAQf", + "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW" + ], + "birth_scan_exhausted": false + }, + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA": { + "address": "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 15633 + }, + "first_seen": 1739951269, + "birth_edge": { + "sink": "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA", + "sources": [ + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" + ], + "value": 3000000000, + "signature": "2GUhQXYjQ56ULyk5kFQ5pfduXNtqXhiikEMcLeZBaVDu2zrQfTDkt9bpVV6ETHzmmXaJn7oyGxu73eK9FqtRTF3g", + "slot": 321645004, + "block_time": 1739951269, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR" + ], + "funded": [ + "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF" + ], + "birth_scan_exhausted": false + }, + "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7": { + "address": "6E4S3XAYuBbznNFrcGtWJu1u7UB5jT9K536zybvYxSd7", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk": { + "address": "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 142 + }, + "first_seen": 1784420501, + "birth_edge": { + "sink": "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1488750000, + "signature": "5reLwzaWen3xeyYobmDJv4AWkboVwkmQea8aqnBXo6AW86tVotCN8aD4HVUseVngB1i8Nz7bWPVayfkXMjq2NZWd", + "slot": 433791379, + "block_time": 1784420501, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S": { + "address": "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894225, + "birth_edge": { + "sink": "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S", + "sources": [ + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr" + ], + "value": 849089919, + "signature": "5mpXMBqiqCUXVfjKe2fqnfr1yXBFt2MnSb5sU4PhDCru1KLnWw7Aji9tVP8ywUoP2X2yAAsgDnLsybmSY33VB9LJ", + "slot": 394707780, + "block_time": 1768894225, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr" + ], + "funded": [ + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx" + ], + "birth_scan_exhausted": false + }, + "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq": { + "address": "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 59 + }, + "first_seen": 1784676427, + "birth_edge": { + "sink": "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 49804000, + "signature": "2ksPaRqePKa3tLNgb6dfJ4xGxBvHsRJuNXeikfUEuupy8NvcTgf3AL8cq7AZRYALiWDeKz2BWxhNZdDHxdVEoDQg", + "slot": 434397667, + "block_time": 1784676427, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6ZPorXXoNedd3u2unPzZRg18LDrE4CQZcUarumuhZdbD": { + "address": "6ZPorXXoNedd3u2unPzZRg18LDrE4CQZcUarumuhZdbD", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "6n5o3qNBAjqVjSGZ8qDWoWAHZdxWKpENVh4sJMLzmwL9" + ], + "birth_scan_exhausted": false + }, + "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx": { + "address": "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1784813668, + "birth_edge": { + "sink": "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx", + "sources": [ + "DkzL477CEJQSynqqqStK378vfCjzxRMvotCz8xdoVzv9" + ], + "value": 246791375, + "signature": "4N7mvNBVzA7HcNpvcfeiHDkJ2U651AKYZpyMcQPpKfpDVCoSxw37tJaJq2njRNbs1kguudKXmp2YeXczfsazvWE5", + "slot": 434724624, + "block_time": 1784813668, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6n5o3qNBAjqVjSGZ8qDWoWAHZdxWKpENVh4sJMLzmwL9": { + "address": "6n5o3qNBAjqVjSGZ8qDWoWAHZdxWKpENVh4sJMLzmwL9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1780177153, + "birth_edge": { + "sink": "6n5o3qNBAjqVjSGZ8qDWoWAHZdxWKpENVh4sJMLzmwL9", + "sources": [ + "6ZPorXXoNedd3u2unPzZRg18LDrE4CQZcUarumuhZdbD" + ], + "value": 482662864, + "signature": "4pHdMusAtwq9ooccUsTDchzRvNCD82ykwieZy5sw65LF1dwTWjHZ652giexigGUd5qkTGu8abDzz55jdASP5hDBM", + "slot": 423239737, + "block_time": 1780177153, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [ + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM" + ], + "birth_scan_exhausted": false + }, + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY": { + "address": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19398 + }, + "first_seen": 1774644767, + "birth_edge": { + "sink": "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY", + "sources": [ + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv" + ], + "value": 5972990500, + "signature": "2FGgEReXPwdXR3S6e4R5wxsotsqmfJU3EEC2QmGDtQF5WxGAspSae7s8GWY2FGx4o9u1oyWSsWMrPznZdTD7KNkn", + "slot": 409284086, + "block_time": 1774644767, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB": { + "address": "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT" + ], + "birth_scan_exhausted": false + }, + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S": { + "address": "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1310 + }, + "first_seen": 1781821301, + "birth_edge": { + "sink": "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S", + "sources": [ + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk" + ], + "value": 600000000, + "signature": "3B7AsVriGEHPW4TRFBkaFhV6YD4RDZ1w7g4UwNtT7psQb95ZMifH6v4QDUwL1k42tLpqhGqiExaje5LTEMkMBwQ8", + "slot": 427377690, + "block_time": 1781821301, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk" + ], + "funded": [ + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ" + ], + "birth_scan_exhausted": false + }, + "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB": { + "address": "74pBdqs9niFM5VUNwVMCQDK7TQhVB3ACKUEJ5DvmGThB", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz": { + "address": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 963 + }, + "first_seen": 1774473115, + "birth_edge": { + "sink": "75kgx5QhMuCXB41uhorbYHfRFqEkVFQChTurtVAnwHJz", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 34895500000, + "signature": "3omGYaNeXk9VjMv3rTp9aoaJrkED6b9jTiHoPx4UbRupiWmUn7op8udTBwguzph7YGJWQpNEhnv1FFDskSRsZANh", + "slot": 408844844, + "block_time": 1774473115, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g": { + "address": "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g", + "owner": null, + "executable": false, + "signatures": { + "Exact": 387 + }, + "first_seen": 1783864288, + "birth_edge": { + "sink": "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g", + "sources": [ + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99" + ], + "value": 15184373714, + "signature": "H1qHMYUqhmFjKH3uLfmAM4qZqHch53iJgKFsf93aSE4nfPZujKefZuX633Rn2aSk1PkWTbw341zXYNTC1ZNBTqT", + "slot": 432444990, + "block_time": 1783864288, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3hfQqc1Y7XVJnfomBpkkqTixrVSPL6ArzY8e64gr5E99" + ], + "funded": [ + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE" + ], + "birth_scan_exhausted": false + }, + "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE": { + "address": "775QCcN3ZYXMx137YbtogqyoX9X82b8qf3FrzauqHLWE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1750857325, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem" + ], + "birth_scan_exhausted": false + }, + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw": { + "address": "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 277 + }, + "first_seen": 1780094619, + "birth_edge": { + "sink": "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 2991500000, + "signature": "84Spr3goYUhDoyS5JCevCc1nDRjo6vWCWeKajzmd3qHWAmsNkBoEjw8322ajWKS5GJyVShK56h1ZUh2VdxtBeCz", + "slot": 423031442, + "block_time": 1780094619, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ" + ], + "birth_scan_exhausted": false + }, + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme": { + "address": "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1712133901, + "birth_edge": { + "sink": "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme", + "sources": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "value": 981006000, + "signature": "2718Lqzzrche69Pqhrt41GqkDBghW5SrqpxVVjrYV5fDNZ2tdipmhyYVKaXEj7Fex4h5mF5NMJ5QU8ohazEukjZG", + "slot": 258093459, + "block_time": 1712133901, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "funded": [ + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW" + ], + "birth_scan_exhausted": false + }, + "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF": { + "address": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1758995033, + "birth_edge": { + "sink": "7H9L8oAErukFwj4gEf4yzxUtfWrjWpW6gRDctzCkTLGF", + "sources": [ + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" + ], + "value": 10000000, + "signature": "5eJNQ4bhKNQYiTVSpUhrHjGpFSs7Ft4GKLrJWqAK5BTnkuzd1xqVV8P1FcpXKG6Zb77U9qdDkyVN6gz1UYzoeB1T", + "slot": 369652410, + "block_time": 1758995033, + "ambiguous_attribution": false + }, + "fee_payers": [ + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x": { + "address": "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894183, + "birth_edge": { + "sink": "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x", + "sources": [ + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha" + ], + "value": 849124919, + "signature": "4b7V78kfU1AxK6fRrczLLEdLEkMQfF6AurV4jSBMBVLEKstbLTQ65VSPjVyWmyRmz1VuMQNDC7vV1gQDAiLtLdrV", + "slot": 394707672, + "block_time": 1768894183, + "ambiguous_attribution": false + }, + "fee_payers": [ + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha" + ], + "funded": [ + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ" + ], + "birth_scan_exhausted": false + }, + "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW": { + "address": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11302 + }, + "first_seen": 1728079811, + "birth_edge": { + "sink": "7Ku4KXWtQXXSp3AQrN5VHZv8nSgCJmSmQ4sDLZXSGiUW", + "sources": [ + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" + ], + "value": 200000000, + "signature": "4rvwJT124GYcZAHHJJBqaZ9YRgnxYLF4nQ58wLbJSr1Z9BgH2NXyKvMRRCbN7CUWooZ9BMNPYAACSwPAqq7NgLvt", + "slot": 293725783, + "block_time": 1728079811, + "ambiguous_attribution": false + }, + "fee_payers": [ + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG": { + "address": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 27 + }, + "first_seen": 1784565160, + "birth_edge": { + "sink": "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG", + "sources": [ + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob" + ], + "value": 232000000000, + "signature": "28bt62zYkiihXsM3o7rp2D6YS63G8CpZwUzpqqp2b8jNzGoUih1PWx9FGMdwfQFAEdRGTgz15pPQ8j9uZgDeyvw8", + "slot": 434133197, + "block_time": 1784565160, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "7h3fgsJgJGXkUtqvtApMJbvJugMfzS4yz9wYoqsMqjjs": { + "address": "7h3fgsJgJGXkUtqvtApMJbvJugMfzS4yz9wYoqsMqjjs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11896 + }, + "first_seen": 1682602307, + "birth_edge": { + "sink": "7h3fgsJgJGXkUtqvtApMJbvJugMfzS4yz9wYoqsMqjjs", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 2443901080, + "signature": "4oBaFynpk5oq4YegVwsdYZei12eyBhFZXy1DHW264fAiQah9bA4Z22RLchZQeTyGsSXMSPbTuY5Hw83rUsMsuYNd", + "slot": 190783160, + "block_time": 1682602307, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7" + ], + "birth_scan_exhausted": false + }, + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j": { + "address": "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 72 + }, + "first_seen": 1682118021, + "birth_edge": { + "sink": "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j", + "sources": [ + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + ], + "value": 11744752320, + "signature": "pALYeH3rq18atGAUHgbqmMBXckY34TfyqKywTwxPzmeg23rXbtbqW9mf3NMct3WsizeZszfuwskkXvbGefvGRRA", + "slot": 189742217, + "block_time": 1682118021, + "ambiguous_attribution": false + }, + "fee_payers": [ + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ" + ], + "funded": [ + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" + ], + "birth_scan_exhausted": false + }, + "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG": { + "address": "7rMVWenGQ7YxEdv2eq5pyWx3jYL24E2Tjn9mVsaWkjcG", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP": { + "address": "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP", + "owner": null, + "executable": false, + "signatures": { + "Exact": 999 + }, + "first_seen": 1784462436, + "birth_edge": { + "sink": "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP", + "sources": [ + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho" + ], + "value": 49648773129, + "signature": "3xd1Te2qtTYHjh2ohyorXsFLgxgWxaveB9tfyGzszJf9MmM1XHhFzQoi5C5ftnUWhXMPRvJpBuHpFidFgZVh2i3g", + "slot": 433891472, + "block_time": 1784462436, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho" + ], + "funded": [ + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm" + ], + "birth_scan_exhausted": false + }, + "7xcCLxFxNxGdj8XPTveMh7gZ8vhEowe6Si9QpoFuCZai": { + "address": "7xcCLxFxNxGdj8XPTveMh7gZ8vhEowe6Si9QpoFuCZai", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1908 + }, + "first_seen": 1732470817, + "birth_edge": { + "sink": "7xcCLxFxNxGdj8XPTveMh7gZ8vhEowe6Si9QpoFuCZai", + "sources": [ + "C8nKoCYa6vbtv51Gszak8W8UoMzCu32ks56rDYdMpwwr" + ], + "value": 1082181912, + "signature": "GrdeMQqkwcSyfBRLPEdpSJavrReG9wQ2mT9FbjmxSH7JqVWuM5UxfwvHUQya2UiuVUWxUUBCKEpWXaucYhUnepz", + "slot": 303378533, + "block_time": 1732470819, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FzZ77TM8Ekcb6gyWPmcT9upWkAZKZc5xrYfuFu7pifPn" + ], + "funded": [ + "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v" + ], + "birth_scan_exhausted": false + }, + "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ": { + "address": "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 32 + }, + "first_seen": 1784389227, + "birth_edge": { + "sink": "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ", + "sources": [ + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb" + ], + "value": 807208726, + "signature": "3vny3LuBCtnjHmNBUw4YGWxGiJdB6MDgCTTwB85ixPF8urpfbhjUiR3DJvptVkoB82k8MLKxgudAvq8m5CPLHCG", + "slot": 433716525, + "block_time": 1784389227, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH": { + "address": "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1705 + }, + "first_seen": 1715894645, + "birth_edge": { + "sink": "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH", + "sources": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "value": 106710780, + "signature": "xDgEvEFQw8Qer5vQHtG755NyT2FHXPnFV9CPvhuvZn4NfZ3b5tWrUL5wDcGVyBDVSSA13r7Y6Tgtdp2mjrmHZrH", + "slot": 266193828, + "block_time": 1715894645, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "funded": [ + "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo" + ], + "birth_scan_exhausted": false + }, + "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH": { + "address": "88dcs7UaNnNP49hMjoiiARwAJ51iWDyug7n1TwKh4DH", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ": { + "address": "88xTWZMeKfiTgbfEmPLdsUCQcZinwUfk25EBQZ21XMAZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1697388134, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "7mmNdLVUXMwPXgBNYkmPfF6ayBgGRxo7GTKKcg84NX5j" + ], + "birth_scan_exhausted": false + }, + "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u": { + "address": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784630445, + "birth_edge": { + "sink": "8BuCVJzZnkbCYoqndQDbCsBqhRe3ZTyzQLTZNyM5ww4u", + "sources": [ + "5pVN5XZB8cYBjNLFrsBCPWkCQBan5K5Mq2dWGzwPgGJV" + ], + "value": 6831797746, + "signature": "4V6TatwrgYYdAXsbYZ58LgACqMKm51JVfQ6cgiJvjtXwvz8NPjZbhsrz9j2QvwKXXv18bkkgtJScAaw5wZy5sKZk", + "slot": 434288158, + "block_time": 1784630445, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5": { + "address": "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784379999, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ" + ], + "birth_scan_exhausted": false + }, + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7": { + "address": "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7", + "owner": null, + "executable": false, + "signatures": { + "Exact": 176 + }, + "first_seen": 1731698151, + "birth_edge": { + "sink": "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7", + "sources": [ + "7h3fgsJgJGXkUtqvtApMJbvJugMfzS4yz9wYoqsMqjjs" + ], + "value": 1000000, + "signature": "4m5a1LNx7X8atKa6KpoaqkGVHX136UYXQA5CaaFLcfaWHthoVdR3an2dBPmuCvNezb7qcM8AQ38BLbKpNHeLXp9b", + "slot": 301741144, + "block_time": 1731760300, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4ritJBeodrzcSDDeAJbKafJniczjYhAfsoZRXTQq9h82", + "7h3fgsJgJGXkUtqvtApMJbvJugMfzS4yz9wYoqsMqjjs" + ], + "funded": [ + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC" + ], + "birth_scan_exhausted": false + }, + "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT": { + "address": "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784049521, + "birth_edge": { + "sink": "8Tg9Zn8ixfzcf3TetFi9sXJHexJ96CPPMfjsQ8wHdTtT", + "sources": [ + "6wWVbLeHDVHvCCsLprB85jkbU1JhS9VezABT2ZZFJ5BB" + ], + "value": 2110033428, + "signature": "1tK2a5Q2E19ZBWkhkhMDf36cuv8NZD8pcRFbVhxkR4kedkEjLsxS7dD1QsKHuZ1T77XRbGiWQx9fnQWcDtgLQmD", + "slot": 432895720, + "block_time": 1784049521, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH": { + "address": "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784564457, + "birth_edge": { + "sink": "8UZNFX1YJ1ohT7JEwyufr856h9QaFtRHJ5RMochELSUH", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 156610510360, + "signature": "3HmDQGGFDNXPquCgrBn4oFueH8EcudAUohYxWiRVekmuX53cZhFotqNnLLPzUEeBnnhHikzZWtUwuP2M6K8EZq1L", + "slot": 434131562, + "block_time": 1784564457, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U": { + "address": "8a9cyaJieNimjEtch36dF8eZSACQKUQKhg1V65vzcR7U", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1782852080, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ": { + "address": "8b9c7XAStj2dKpqk3U2SsXcxh5jPkuFTExUhnFZo4iFZ", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "5cX6A4RZ3wJHxTdRMqxDRCkBtdo43ZA4orB2QRZxUb4p" + ], + "birth_scan_exhausted": false + }, + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx": { + "address": "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784217778, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC" + ], + "birth_scan_exhausted": false + }, + "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r": { + "address": "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r", + "owner": null, + "executable": false, + "signatures": { + "Exact": 10 + }, + "first_seen": 1753767029, + "birth_edge": { + "sink": "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r", + "sources": [ + "FWKrAi8ZDaeKx9NQoJmLGyMoujLUUjsM8pqmghFqrtaf" + ], + "value": 15098148051, + "signature": "2fHx4G4K3zY2kCZJ1oh6S1yJ7EhDMvGZKWiwauLybaV8r74zoWQhxbCq8LmXjnD1GzzzgXQmsGGn3ZU7faY1A6df", + "slot": 356448276, + "block_time": 1753767029, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FWKrAi8ZDaeKx9NQoJmLGyMoujLUUjsM8pqmghFqrtaf" + ], + "funded": [ + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" + ], + "birth_scan_exhausted": false + }, + "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E": { + "address": "8fenZTDtD27NgQoApjRVTPnZyTEgdjuxkWcQZiPVRS4E", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1673079363, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1": { + "address": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2157 + }, + "first_seen": 1684141322, + "birth_edge": { + "sink": "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "sources": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "value": 90000000, + "signature": "52ZPH7cfAzwauMYh529GgHWhTzRwReNdJyWYr5ZugsAB5gvSmP6CYuCV1U79B9SLxLjk6qy5nsDUmao8Xa5y5EBB", + "slot": 194024003, + "block_time": 1684141322, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "funded": [ + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6" + ], + "birth_scan_exhausted": false + }, + "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1": { + "address": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5535 + }, + "first_seen": 1747156899, + "birth_edge": { + "sink": "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1", + "sources": [ + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" + ], + "value": 1000000000, + "signature": "2cATjnRUeA8sBSjhU4udUTHwqFk5cLQfx2mMZ7isJfKvdfGEJNn4q3Ex72Ww3fRNX7JUQTGpy25R6FVdfFwRP5dV", + "slot": 339786892, + "block_time": 1747156899, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb": { + "address": "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1391 + }, + "first_seen": 1778270115, + "birth_edge": { + "sink": "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 31543190000, + "signature": "23eCmHrxuyS6rMmjNNDsJtUZk4vesD5maoraeVjWmqKuLe1yeuELQgEDaqfBDUWSTrkM3iuQFUWAsPpkjAiD6meJ", + "slot": 418463518, + "block_time": 1778270115, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr": { + "address": "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1774937240, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" + ], + "birth_scan_exhausted": false + }, + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha": { + "address": "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894180, + "birth_edge": { + "sink": "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha", + "sources": [ + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t" + ], + "value": 849129919, + "signature": "z18ostGmqULsMip19y3QrR6sRFfittsoYwNTF1SjPA5iLq1dJfQw8yJvyWKryxBGjhXnhfnVPiuUjpFyggjQj8v", + "slot": 394707664, + "block_time": 1768894180, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t" + ], + "funded": [ + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x" + ], + "birth_scan_exhausted": false + }, + "9HGyyJnWK4b67E97bB4SXpjbxpYmTBMaKNmWmfmYe4DK": { + "address": "9HGyyJnWK4b67E97bB4SXpjbxpYmTBMaKNmWmfmYe4DK", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 950 + }, + "first_seen": 1781405177, + "birth_edge": { + "sink": "9HGyyJnWK4b67E97bB4SXpjbxpYmTBMaKNmWmfmYe4DK", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 55361142572, + "signature": "48U5vGEMeabvwxYjwxueLZukbtdRbGFKABsAVrmP9pViW24nMA2vTcgZLqZSqL9js1Muve2grFATYhgeEstyoCNp", + "slot": 426328548, + "block_time": 1781405203, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k" + ], + "birth_scan_exhausted": false + }, + "9Kjd4PMdZFQrzuGvv7PuMA1uXTu433GP5CqHGNaJhtja": { + "address": "9Kjd4PMdZFQrzuGvv7PuMA1uXTu433GP5CqHGNaJhtja", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 8 + }, + "first_seen": 1670977110, + "birth_edge": { + "sink": "9Kjd4PMdZFQrzuGvv7PuMA1uXTu433GP5CqHGNaJhtja", + "sources": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "value": 4990000000, + "signature": "4XqtekWfG6hXY9Qbx1nR2FC44qNATqBD6uVDxJ5ByWRvavpCr6dMpGaoTFDXHqS2NHbbsdHBgqm4X3GpvNKxuoP8", + "slot": 166727018, + "block_time": 1670977110, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "funded": [ + "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP" + ], + "birth_scan_exhausted": false + }, + "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu": { + "address": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20518 + }, + "first_seen": 1743263000, + "birth_edge": { + "sink": "9SMyYdEWbeu8beve9BShnWFDJeWw4hJ6Mmcd2i5TJPpu", + "sources": [ + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" + ], + "value": 770000000, + "signature": "rWZNiWUUErqSurTeKbooG5bPJgChg9SvYyYCsgNUy6AXuiHTsyVDYCyzCmYssFrs94PhG2ok9FwYAkVEDrK6efw", + "slot": 329977339, + "block_time": 1743263000, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu": { + "address": "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 432 + }, + "first_seen": 1783530592, + "birth_edge": { + "sink": "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu", + "sources": [ + "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o" + ], + "value": 3608900737, + "signature": "39zbFyzcd2pWwLEQBD1gB1qWrjTekKNiicL8TpyjTAoyLABpT518QVo9WVz8yxdzCJF1WiRcvuHzJRR85WFBXjfv", + "slot": 431630423, + "block_time": 1783530592, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn": { + "address": "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784565684, + "birth_edge": { + "sink": "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 841356800, + "signature": "4Y2A99s86tDM9Ef8DQ62ED34YhLejSy9dfkYDHR2rAi6kcnSNvg5pW1F83RyJuHK4DPoxkN5mAkvqvyA1EHJj4Wd", + "slot": 434134426, + "block_time": 1784565684, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh": { + "address": "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894217, + "birth_edge": { + "sink": "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh", + "sources": [ + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" + ], + "value": 849099919, + "signature": "4UhWatoFQvHLdrg56s4qh3NjJaa9jpsS55YeGbi2FwyNHuoqfnZBLZx3npfoyjhFbvMaNm5vxYmYSAkjPdK2Gioi", + "slot": 394707760, + "block_time": 1768894217, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" + ], + "funded": [ + "5NXwVuYB6rEHcka2Ey66aoQ7e3bjzC6oDr2a2NkVdwjr" + ], + "birth_scan_exhausted": false + }, + "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp": { + "address": "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 205 + }, + "first_seen": 1775579392, + "birth_edge": { + "sink": "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 9131905000, + "signature": "3zWVxUN5PpQhBBHTb7q2iVdGYZWzYX96QFS1nC467YtQBSKuqjZzLFQEPh816DEVyZmtn6xdFvssDpQqdopjccPh", + "slot": 411667852, + "block_time": 1775579392, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi": { + "address": "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1780957515, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU" + ], + "birth_scan_exhausted": false + }, + "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY": { + "address": "9zfFDeoRj7DVfLouw8LMPdKkAMyStByNyvzAn1vRPhBY", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR": { + "address": "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784454004, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3HeLU9KQtGBDCVXMHCbkYKq1SFZrkWm7X2Yk1CvgeRhL" + ], + "birth_scan_exhausted": false + }, + "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf": { + "address": "ABNL41SwMLVRaCq6okEWsWyvLwm56nazUNwMrTHgmuyf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1778790052, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3CNLu4mPXbvcy3VzunxzUdXgxjQHeASYjCty8cBbnkGt" + ], + "birth_scan_exhausted": false + }, + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW": { + "address": "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1782173460, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "5muie7qQ8uTXGM9uEHJyR3WYQoyHeRA9Npnkp3L6ibW3", + "9HGyyJnWK4b67E97bB4SXpjbxpYmTBMaKNmWmfmYe4DK", + "3v3yjUVK93vdoAdgZ5NuQPHC6ERXQ5fbduELSaYT5Kze", + "2BXesxtW6sKLkBnSiWupFjfgZjauUycNxgzCioEhghHJ", + "6N77qEgunFqkU99DyZAaqNh1TENimfVVfSKegKm9a4qk", + "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", + "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "8saHrxj1e3BGeAEh8zh8ssrniGwE5JtJVxy5V9w94hrb", + "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "4XXUvTDdke6d4hJQ7XF9KwkkkUMYsTbjrNF3tSizbYjr", + "22mLe7XtDZUsHph4YNkcTAK9MZzByXLbi4Mf28mQT9ma", + "6ZBSAUibYQVaHqvuaYqxK62jAaZhFzKgetSxoMYDRskq", + "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", + "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "4tyoKRazcMQH4ZkZoKBU3TU9QuWPs5K1Pmg7G9krEKGk", + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "3eynayRMaDTLK3RYyF2yy34tunwqhWCg3sF2kvg1JGx7", + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "9vZW7UvA7qUuJJBqn3KGtdQTCNsxW9hKbzwAPjt2sTmp", + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM" + ], + "birth_scan_exhausted": false + }, + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx": { + "address": "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894233, + "birth_edge": { + "sink": "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx", + "sources": [ + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S" + ], + "value": 849084919, + "signature": "4MQSbWiBDWsVRVVKMjprc9fMfevDFzvAJJDcTd7PPwkGKXTfqJtB5frc9naHWzJv1S5yi3XLnmKZPMEbQx95M7aU", + "slot": 394707800, + "block_time": 1768894233, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6SgPPUvPMVHSrnfmuD4XuW8KgArrGcxYmm8diuRsGH8S" + ], + "funded": [ + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB" + ], + "birth_scan_exhausted": false + }, + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ": { + "address": "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784653278, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1", + "4bnaii1RywBu78QnxvBWQq7TQP5zGUNY26ESM1g7wF16" + ], + "birth_scan_exhausted": false + }, + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW": { + "address": "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW", + "owner": null, + "executable": false, + "signatures": { + "Exact": 711 + }, + "first_seen": 1663704019, + "birth_edge": { + "sink": "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 3079735480, + "signature": "24tTRqCdoMDLxwi41jhddFdaqydjZowSRXZHW2xuBqNttskX1ej72PhQUDqGoiF9jY5u9i8kbdyMcx5MMUrRWXsD", + "slot": 151573078, + "block_time": 1663704019, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq" + ], + "birth_scan_exhausted": false + }, + "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t": { + "address": "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894166, + "birth_edge": { + "sink": "Agiaa4qhKVtugGZ4WdpachVq7d65odGx5DPxnHLGhv8t", + "sources": [ + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe" + ], + "value": 849134919, + "signature": "2HfqS2otjA4GU4BjEiL2ei8YhTcqcwpSZYh7L5E7UfwfaigJ78sP6qi22gs9zZjcZFk8S3FkSK1tf7Zx5CSAKmYE", + "slot": 394707631, + "block_time": 1768894166, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3JpzxnLEQtYiTaK9xShkDAZBaetHw1RADgoqJKiJn7Qe" + ], + "funded": [ + "97AGx1pp3jrQAqAzwnA54TyrjmtP4RTkV6LycMrTdwha" + ], + "birth_scan_exhausted": false + }, + "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ": { + "address": "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2412 + }, + "first_seen": 1781821309, + "birth_edge": { + "sink": "AnW32XEjUw5179U7vvGgZPHAbMjoGUr2mPPtTp86LhyQ", + "sources": [ + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S" + ], + "value": 500000000, + "signature": "4wVPQe58DmcHwkdVmHuvqWFGnQZhQ4ndiumpcrvNSYx6bL2bBCk6CCNeWpKDPzvc8Y68RpfFKuCrfScRuL2VaE58", + "slot": 427377712, + "block_time": 1781821309, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6ymhGyyuuAuL15w3nkbBw6aERZnmEvHLsWA3otYFRV2S" + ], + "funded": [ + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu" + ], + "birth_scan_exhausted": false + }, + "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo": { + "address": "AoRRRXMiRpZUqyQp4JjbuYes7kTrvgw4ZVn5uXsQbuXo", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk": { + "address": "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1785002824, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD" + ], + "birth_scan_exhausted": false + }, + "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX": { + "address": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 16389 + }, + "first_seen": 1774739788, + "birth_edge": { + "sink": "Azh97tUtsiFmLw4wLBiaC1cwABfbqg71ycmRVt8kkUyX", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 3440555488, + "signature": "3YjE4YpSZT1wXeSk57dn8UrLTMQEWkLqF2r7LpApgEvjMt2h6k5GrkEQfeu4JtsHkPZJvVKqGZkhzKeDxFvroTJj", + "slot": 409527481, + "block_time": 1774739788, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ": { + "address": "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 107 + }, + "first_seen": 1783458420, + "birth_edge": { + "sink": "B3oz9sgGTAbmyizp1WzxYvxkAueh1hioGyxpFXD5BbhJ", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 113580000, + "signature": "5ZgjZruTRMzF44tmPbZD6YJgg6LZQjkcbMBGyezVXdjbnb3BX77K2dUaVhG5gQDApdA3ufCKKDUZbqZtX6pMkvE3", + "slot": 431453303, + "block_time": 1783458619, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ": { + "address": "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 618 + }, + "first_seen": 1774728729, + "birth_edge": { + "sink": "BBRFSKHCnbtwZC2XRAw8w1tDb3Q5y5eNj9KnjzKyroEZ", + "sources": [ + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" + ], + "value": 11650417543, + "signature": "5TnbPcr9zLALAWF82aL7PVBNbX7PkaUTzdXgAWEnvZAeE8n5cYzXhyEKh438w9uj6LrF316rj73McA84g5DHueHM", + "slot": 409499170, + "block_time": 1774728729, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8QCVZ7KL65pBhUxbvfwbyyg4ronxNAEXbDrGLYLrRNn5" + ], + "funded": [ + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V" + ], + "birth_scan_exhausted": false + }, + "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG": { + "address": "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 6 + }, + "first_seen": 1784519630, + "birth_edge": { + "sink": "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG", + "sources": [ + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB" + ], + "value": 104889883544, + "signature": "5162f689Bxmi9PA5Fj9QVxBuxjHrWqbJnoHVVjnmUg4HB9j8XnMvUmLaugVAcyEnAbpiPQNQiukSiE2D2H2AUKaQ", + "slot": 434026767, + "block_time": 1784519630, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC": { + "address": "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1748177913, + "birth_edge": { + "sink": "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC", + "sources": [ + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7" + ], + "value": 1029000000, + "signature": "CfG62TFDb4cuNH7FNWz4Cr2Ga14r88NoTXbSDkWmGSDhFTqxdKxinZcyAeSJ33eE1VPiqfwfgcBcmyMRijYEDNb", + "slot": 342369414, + "block_time": 1748177913, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8QoTaGN2fBjLfeeT8QssfCAB77GbRoA1TvBg2mS8gxB7" + ], + "funded": [ + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd" + ], + "birth_scan_exhausted": false + }, + "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM": { + "address": "BKQqoatcqyoWoiUnHDJM2FW23rtwoMiCNnVbMECASGPM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783098068, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm": { + "address": "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", + "owner": null, + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1784580094, + "birth_edge": { + "sink": "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm", + "sources": [ + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP" + ], + "value": 50607243678, + "signature": "2h4gvshcwfRhkDXQhiLMAWYTWM4xMUnW8iGdJpEnhQRUYZy5DuMQma319GnL8wCprP7MTXGgb8ymBWvQ1jwTwoht", + "slot": 434168171, + "block_time": 1784580094, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP" + ], + "funded": [ + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ" + ], + "birth_scan_exhausted": false + }, + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m": { + "address": "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3738 + }, + "first_seen": 1632342722, + "birth_edge": { + "sink": "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 2099995000, + "signature": "56mSUdyemWLDwZbWDjZCUhJDHGqgJaGiMGVKSDovzLsM54Lc26JshmZPgz4R3N6Se2B12z8CkeAQGsQz8ELb4XjJ", + "slot": 97853541, + "block_time": 1632342722, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [ + "37Sr8FuzLEviyv8UD1Whz4eeY3ocyy7TXKqPEEpCRRmT" + ], + "birth_scan_exhausted": false + }, + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K": { + "address": "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 581 + }, + "first_seen": 1775667915, + "birth_edge": { + "sink": "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K", + "sources": [ + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr" + ], + "value": 12399485120, + "signature": "1hqU1oga3ZT4F3uT5iAm3W4zB3eiH9vd2Rop2Rtyva35zubCfiYZCFgAaSaBCD48a3iTz1aL9zpTnZ4JRq6yZE5", + "slot": 411892813, + "block_time": 1775667915, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8taRrDLR8XNqeMSzA9cKFHuanfiHe428MxxK6jFNQBWr" + ], + "funded": [ + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" + ], + "birth_scan_exhausted": false + }, + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE": { + "address": "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE", + "owner": null, + "executable": false, + "signatures": { + "Exact": 930 + }, + "first_seen": 1784212794, + "birth_edge": { + "sink": "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE", + "sources": [ + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g" + ], + "value": 23308100465, + "signature": "48hdxxJEtMvxPq47QrfdcsDqMDNP8FzBPgM3RN4YfsmFxFrnsRSuNHEdiXcnjYTxujSyVAooQqfo271w8BCoKFVk", + "slot": 433291812, + "block_time": 1784212794, + "ambiguous_attribution": false + }, + "fee_payers": [ + "762tKYCb7d8EDuCKnmcdjunrtSNGXe8yBwfufCQfWf9g" + ], + "funded": [ + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva" + ], + "birth_scan_exhausted": false + }, + "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38": { + "address": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 567 + }, + "first_seen": 1759849779, + "birth_edge": { + "sink": "BYSTYBFB8m4AWrqdEPzJKLYqWCVHp6o5AvRZp9HyHu38", + "sources": [ + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + ], + "value": 1000000000, + "signature": "55aNfhpFmWyptpJ52kvWWSpaHePRjZLDNbf9kzwSKgUtvvcp4XFhGpKXbU5tVD4wYFh3DrtjJpfnKfLoyGKkadYN", + "slot": 371813843, + "block_time": 1759849779, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2net6etAtTe3Rbq2gKECmQwnzcKVXRaLcHy2Zy1iCiWz" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6": { + "address": "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784392548, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9Kjd4PMdZFQrzuGvv7PuMA1uXTu433GP5CqHGNaJhtja", + "79nbbtVw7yJwUajnDEEDRZXjjBebCUXBLP7EdvZJdLme" + ], + "birth_scan_exhausted": false + }, + "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo": { + "address": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3597 + }, + "first_seen": 1736334578, + "birth_edge": { + "sink": "BnZAVY6xMaqTwwW85e2LyKchm8yJrvGv6eA3qBB8sAXo", + "sources": [ + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH" + ], + "value": 170000000000, + "signature": "2MLS8L4uUc8L3nNZpej5csKZ3d5JuNqKGkUjpX1E1ebmqEBzcxBP3ZccC4FvbAgAFWchKPoey8mhH98JCnCxCQ9x", + "slot": 312653033, + "block_time": 1736334578, + "ambiguous_attribution": false + }, + "fee_payers": [ + "848txtzQ713KF6PY9cQAtPTP2W6pbrrLntyYrZMsoDbH" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw": { + "address": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 155 + }, + "first_seen": 1780608496, + "birth_edge": { + "sink": "Bqqa46ZcQKQn4Rj8nCV7hgPYWJ3TLaSbrP3HCUwbzWWw", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 3965000, + "signature": "2jPiYFrpAtgu9YXus8V7NPkAWW6xWy5zBdh42P7S5sticdDDVgPGo91ne8yDeLjXhZHyH6zxZnWg69yPXRApwheC", + "slot": 424324895, + "block_time": 1780608496, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ": { + "address": "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2999 + }, + "first_seen": 1780098381, + "birth_edge": { + "sink": "BvDFyWV3SvDG4c56tvjdzPkfS29jEMpCdZdANrkNQ7DZ", + "sources": [ + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw" + ], + "value": 800000000, + "signature": "3owWqGQL5cNVZcCTN235bbaS2gyez3LxGz663Gej6dtmqBhUyrbhJNfoP1VaVryXN47bdKWoxji34CAu6oUPsxdP", + "slot": 423040927, + "block_time": 1780098381, + "ambiguous_attribution": false + }, + "fee_payers": [ + "79PxaD5FvzcGioKAXQYJZSKr7B5fafL23yFeQzVPevBw" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P": { + "address": "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 96 + }, + "first_seen": 1784854429, + "birth_edge": { + "sink": "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P", + "sources": [ + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT" + ], + "value": 6000000000, + "signature": "5KA9mkbPhkRXKEhf1FeiRuxHphiMMkRgq3FsNjkKk2Ra7gRMMqmB7V5gEvocEESMeK24LVsUC24gWjBx7EyiX3Kp", + "slot": 434821643, + "block_time": 1784854429, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis": { + "address": "Bxj3wTgdx2sS2fM9FVFSYTibnuKE1VWkJfHuEk5VAPis", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD": { + "address": "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 627 + }, + "first_seen": 1753997946, + "birth_edge": { + "sink": "C2Y1JPWufvGE3BkosqfRSRpJyWREcBhbH2RzysMDyXdD", + "sources": [ + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" + ], + "value": 123066991, + "signature": "5Rtx3JoWd3XaAGxkckRh6w7aeW4W9M2ZbG8NQt7SDLdunYiy5eHXiF3Kc9Dp8QBMJABzH2T2ctoAzCEZF57ckAAB", + "slot": 357026958, + "block_time": 1753997946, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AxiomRXZAq1Jgjj9pHmNqVP7Lhu67wLXZJZbaK87TTSk" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA": { + "address": "C4vQni1wLtq4QeME8aT6QbfE5G7oSiMD175puFsLkcRA", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "C8nKoCYa6vbtv51Gszak8W8UoMzCu32ks56rDYdMpwwr": { + "address": "C8nKoCYa6vbtv51Gszak8W8UoMzCu32ks56rDYdMpwwr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1732470808, + "birth_edge": { + "sink": "C8nKoCYa6vbtv51Gszak8W8UoMzCu32ks56rDYdMpwwr", + "sources": [ + "FzZ77TM8Ekcb6gyWPmcT9upWkAZKZc5xrYfuFu7pifPn" + ], + "value": 2039280, + "signature": "5JvDwz1GpjepgZy57RRa3QLJETqzfAVFf2UGDcn3Qb5n47necpTwTHM5UvBppqmx1YtmwQYH1MbeaDmCMnNZc72b", + "slot": 303378506, + "block_time": 1732470808, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FzZ77TM8Ekcb6gyWPmcT9upWkAZKZc5xrYfuFu7pifPn" + ], + "funded": [ + "7xcCLxFxNxGdj8XPTveMh7gZ8vhEowe6Si9QpoFuCZai" + ], + "birth_scan_exhausted": false + }, + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr": { + "address": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784599658, + "birth_edge": { + "sink": "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr", + "sources": [ + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + ], + "value": 440000000, + "signature": "4QJkfNCAWjNh2mfYrx9WN6EBe6cCpPUABueLgf4WfqJ5smBUdqbHDcsQXAHT6d8a6q4hFNMvf7jAozFE4jw1rXwj", + "slot": 434214781, + "block_time": 1784599658, + "ambiguous_attribution": false + }, + "fee_payers": [ + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob": { + "address": "CDHhpcpZ6rBK9iurqeJsj8QWbBBVo9tiL85SLojLerob", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 134 + }, + "first_seen": 1784218241, + "birth_edge": null, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "AgmLJBMDCqWynYnQiPCuj9ewsNNsBJXyzoUhD9LJzN51" + ], + "funded": [ + "7Ropb1oWy9uZowkvaGWw88GepGaZ4MivexFrfAa4dJrG" + ], + "birth_scan_exhausted": true + }, + "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh": { + "address": "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784649420, + "birth_edge": { + "sink": "CNXERaCE3x5W2tz9io7RwRJ9utiXumvdXkiLg3AsJ1Gh", + "sources": [ + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr" + ], + "value": 1850143000, + "signature": "3YgFygisXK1X8kyUXV6LzPEcJ36tKUDkuyo7UvX3KdwULNGDe2wLYiB7KSCVcyM9NejVf2Q4Gr2KV2p11SSvGbCy", + "slot": 434333385, + "block_time": 1784649420, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3V11R3pEGSt5XeRaoLDcCdkAB8zVSaKRCm8y3uKBgnsr" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb": { + "address": "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784389224, + "birth_edge": { + "sink": "CWp2pdKdv4KzX2XSHZcVQMTrXSSWpixzJzhuVK52GHfb", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 807213726, + "signature": "2MwPJoYeQHsFvpTXyue8m776mx5fBtFDYgY7hcZpTuz641UR5R8xz7yoe7NXT8e1mRojMFpwKs6Pkdk1fFgSe6zr", + "slot": 433716518, + "block_time": 1784389224, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "81SrfuuBA8DmnX5EofuHKxoMaK95ehbhQMbkY4rLpMWQ" + ], + "birth_scan_exhausted": false + }, + "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT": { + "address": "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 87 + }, + "first_seen": 1784838909, + "birth_edge": { + "sink": "CYuD3VzotR8NPczeP81UKXXiWCZkMNVpK5ga9L1w9VCT", + "sources": [ + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1" + ], + "value": 6000000000, + "signature": "3wsTxnCcjxate2zoeFse3mstAwC5KUkvVH4AWjgi9FTKZmwvm7vjJb7N5EPGR5rpcDePeFVXxopoMKiWRsG67bTu", + "slot": 434784661, + "block_time": 1784838909, + "ambiguous_attribution": false + }, + "fee_payers": [ + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1" + ], + "funded": [ + "BvTuGbZfBr1faAT74jvM1JoP7CrwDSgRgFzP6CtQBe7P" + ], + "birth_scan_exhausted": false + }, + "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB": { + "address": "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB", + "owner": null, + "executable": false, + "signatures": { + "Exact": 44 + }, + "first_seen": 1768894237, + "birth_edge": { + "sink": "Ce4GMytgRiLRsHgRi4xU4fcDvmi2Ab2NqAfkmz1wHxvB", + "sources": [ + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx" + ], + "value": 849079919, + "signature": "ZPtjQoLcno8LwEZaoxwJrAZfMfcHmszyxAA1V2FW6ohJxa3tbczaP1T4s4d5HgxHTxDS7tTjRK5GnV3x55nWPCQ", + "slot": 394707808, + "block_time": 1768894237, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AJeDo4ZxmC7Tjg1eF1gVymLhNxuSR1GyRb64vANCFNbx" + ], + "funded": [ + "BCuDtrKy7MUBa1NpeRVt9aEKumZpavU3NEq7bEdEcdhG" + ], + "birth_scan_exhausted": false + }, + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN": { + "address": "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1784377763, + "birth_edge": { + "sink": "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1299793806, + "signature": "wbcYZ4GZzni8ThU986zLwqQUmhcY4gZfGFBbC1ffpv6TwUkWtxPLvUt6FB3fT9DvuaEKu21bsm6RwpSBVgrkiEh", + "slot": 433689096, + "block_time": 1784377763, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9" + ], + "birth_scan_exhausted": false + }, + "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU": { + "address": "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1784657908, + "birth_edge": { + "sink": "CmFmcBgTumLRGZfU4bNN4fynNnaJ125UgkWNhmmQX6hU", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1996965000, + "signature": "5bJGTURSxfgDoe8QBCjFXtRwNcrBHqLyN8oJ3HjztNDMnbgtzNG11DtZ51MgkqAkcii2BWghA9KB3MitvWTQxF5d", + "slot": 434353532, + "block_time": 1784657908, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94": { + "address": "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894203, + "birth_edge": { + "sink": "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94", + "sources": [ + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf" + ], + "value": 849109919, + "signature": "2XXhJExg7yyA6WH34H5Q99ZtCVavER65hC3V7tf6oNm5onx4KDVoDEpSgAaoupvn721NrE7KdSiMQ4cjKZY1UfJy", + "slot": 394707725, + "block_time": 1768894203, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf" + ], + "funded": [ + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm" + ], + "birth_scan_exhausted": false + }, + "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR": { + "address": "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12753 + }, + "first_seen": 1710763330, + "birth_edge": { + "sink": "CoBwyPttQPbAprmAqhgFQnXD4vxN9d6ERcz4XSNdEzrR", + "sources": [ + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" + ], + "value": 1200000000, + "signature": "3BsEYvaLSedjLWzUCzbGUVGT33rwY35m6mzWnXSLit5zTTJ7vctb7oDjhv1mCdUZLWtRHcLMYSxYz5Dp4hhhc5Nw", + "slot": 254924622, + "block_time": 1710763330, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3vBekALpEMwf59ayddsgc8KJ423gPkxMsd1hwNAKBAN9" + ], + "funded": [ + "68x9JmFHPvunPDFJrSBiwXUhENN8RssLF1u8kg9UrrbA" + ], + "birth_scan_exhausted": false + }, + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz": { + "address": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12758 + }, + "first_seen": 1781801872, + "birth_edge": { + "sink": "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz", + "sources": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ], + "value": 100000000, + "signature": "2B1U3i2nZKDXancwuyt9UxUa7AJcqgiVyuXzZhECVSZwiy5CKYE7xe5KcEgGymmMS2FcifLQRD7PNgDhhE3zGEkJ", + "slot": 427328708, + "block_time": 1781801872, + "ambiguous_attribution": false + }, + "fee_payers": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU": { + "address": "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2660 + }, + "first_seen": 1777272559, + "birth_edge": { + "sink": "D3HSosDAbujBRb3GKcTkMn6K9hzhpoG9KM98SkCwPtVU", + "sources": [ + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V" + ], + "value": 2500000000, + "signature": "ETzNdupbU3xXhkPGEw9Hn8tPN6NDugSpsVob4iHjzcfzccdcUAisMiYbqkwSbCm6AxsfqxZvhjfCCUC3P1kEHRc", + "slot": 415950310, + "block_time": 1777272559, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2Q3W3BXL1M2yEfqqZkJifc9txBpNzSBAd2e2uVtW3a3V" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA": { + "address": "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784868800, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "DkzL477CEJQSynqqqStK378vfCjzxRMvotCz8xdoVzv9" + ], + "birth_scan_exhausted": false + }, + "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U": { + "address": "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 272 + }, + "first_seen": 1757688961, + "birth_edge": { + "sink": "DD5W46FGxDmo6R1VxT5PrbbfTcutfH1acCDg3fQVbY6U", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 93700000, + "signature": "3PeWiircHtHEH9T78i4FKAoUNP3568WEdnJr8PgTPdhPPi958X6NrtthHtGn2r21ovMKKWwCo8TkMtc6rmzw6odX", + "slot": 366350032, + "block_time": 1757688961, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR": { + "address": "DEdwtjvCs6dSadA8b6EQwCP95mXebTqMDK6wh13xhdpR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1776160820, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi": { + "address": "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18105 + }, + "first_seen": 1738500891, + "birth_edge": { + "sink": "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi", + "sources": [ + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + ], + "value": 29400000000, + "signature": "4ChkDoLctkN81tVBKsZMZVqvdAZViy4zrsKb9K8FiiuD4zvuJnzXq2qjMZAnVNqReUqo7Tb12d3knN7qTDW96mZw", + "slot": 317992649, + "block_time": 1738500891, + "ambiguous_attribution": false + }, + "fee_payers": [ + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + ], + "funded": [ + "8rccnjzBQFzPre8GJSLajAYvNzM17RbzASApb2x9rMo1" + ], + "birth_scan_exhausted": false + }, + "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX": { + "address": "DSmB6K1QuFSwXYcuwinqDQQgQ6aSoKuo3uTpuiGq78zX", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff": { + "address": "DVMkhiQe1D8yenuEgsW44NjRn9LfVQjGEpZcez5x7Mff", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784043065, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DkzL477CEJQSynqqqStK378vfCjzxRMvotCz8xdoVzv9": { + "address": "DkzL477CEJQSynqqqStK378vfCjzxRMvotCz8xdoVzv9", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1784813668, + "birth_edge": { + "sink": "DkzL477CEJQSynqqqStK378vfCjzxRMvotCz8xdoVzv9", + "sources": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "value": 2039280, + "signature": "4aCLGjAGKpkimChSRq33MU7uy3sa1TV5aHA6tCKKeynpXDSdSgugjisTHUqALnAQoJCzJmMYBnm1zhaoexv8wvBD", + "slot": 434724624, + "block_time": 1784813668, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [ + "6gHNr2kc8uaiRQry2ZJE8iE6ZhBsg2a6iz2JUNWAGjXx" + ], + "birth_scan_exhausted": false + }, + "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x": { + "address": "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x", + "owner": null, + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1784934326, + "birth_edge": { + "sink": "Dp3XMDK2Tabg5jp2ikxTC6JGBsbzooHvKTiFMokkg14x", + "sources": [ + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" + ], + "value": 54374849153, + "signature": "4Y7SpKFVWPXmK1GJQMsZfuQnxiYyoi7NcJbchUzRemXMGt3DWRUWJBT7L8seY3MymYCvHhSv7XravPdS34KHgmpL", + "slot": 435012112, + "block_time": 1784934326, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2WQCfa31y6Qid5aBAbDE6arARchEochqGufAM6Y9Nsuy" + ], + "funded": [ + "5ZPwW3GtoR8TsnPGdsDmFGd6eeJiaxBHQg8Di8C442Za" + ], + "birth_scan_exhausted": false + }, + "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn": { + "address": "DuSs7rCr7oTLHjM29QQ8NwDViaahTU58qqxaJVyAkgbn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784443666, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2FsWwP7tmDyH2DTpvxRAxVqEyj84hU3qDmt238g65mp9" + ], + "birth_scan_exhausted": false + }, + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K": { + "address": "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", + "owner": null, + "executable": false, + "signatures": { + "Exact": 55 + }, + "first_seen": 1730861242, + "birth_edge": { + "sink": "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K", + "sources": [ + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" + ], + "value": 50000000, + "signature": "BoLFuKgcg38xZSUt14Y5926pkU6LLbaXAKPHPqTzfCKUCYyRNa5T3msnqj7QtwrjP8FUFHxsTKELrjYKA1gUvtX", + "slot": 299749544, + "block_time": 1730861242, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" + ], + "funded": [ + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" + ], + "birth_scan_exhausted": false + }, + "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq": { + "address": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 294 + }, + "first_seen": 1764868637, + "birth_edge": { + "sink": "E6Je9BGuv4SWCqwrRXFbkEuAgKp6fDPLiwrakWHio2Yq", + "sources": [ + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem" + ], + "value": 1390000000, + "signature": "42jLxFrfSKoLo8ZPnaMZfGU56WVo4mrSi1RUEweRK84j2R4vSSRetdHu9HErxgK6eR87R6vt2c7T2C4KUTaDjf4F", + "slot": 384466286, + "block_time": 1764868637, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2jLFUPYbX47drr7pp7kAGoxcsEVtdCSWPTqhMVzkPjem" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do": { + "address": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 203 + }, + "first_seen": 1776858197, + "birth_edge": { + "sink": "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do", + "sources": [ + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" + ], + "value": 1007615782, + "signature": "2KPM9xqT2mfMAqEZDX51t6o3RgYoLnJZgU6NPzZ57YV14DZy8VsnyYxntqnKbhtYAZwE4puvnjUP9PmE5y4aUBa1", + "slot": 414903289, + "block_time": 1776858197, + "ambiguous_attribution": false + }, + "fee_payers": [ + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW": { + "address": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 549 + }, + "first_seen": 1778972678, + "birth_edge": { + "sink": "E9HSTWen7vEE42qLWKvVuS3ToWiEsXvmt2vzhgG76FnW", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 6632702930, + "signature": "vJcCHLLH6FaWPDarJWTvMdpKckbK9yPGhy9qgUgypRyheTmVdPiEMf5vCioqKT82iF8G7VZ95vXGuYWoECTAh2B", + "slot": 420215149, + "block_time": 1778972678, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "CBgkzioYTK6Esxjyt2LeFtbzdWKJALdq7rydGSDiA2jr" + ], + "birth_scan_exhausted": false + }, + "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH": { + "address": "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 369 + }, + "first_seen": 1745361264, + "birth_edge": { + "sink": "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH", + "sources": [ + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn" + ], + "value": 1000000000, + "signature": "5Ugmig4PTLRcf3GTuiY6G47gQn5bxj3UDp4YpKDsvY1JaPwwn1DMU1bQPnTXgrP8wQtrEZfmpLthv8gjADrp7mpu", + "slot": 335243820, + "block_time": 1745361264, + "ambiguous_attribution": false + }, + "fee_payers": [ + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o": { + "address": "EYj9xKw6ZszwpyNibHY7JD5o3QgTVrSdcBp1fMJhrR9o", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9dht891ABzDAy3tcvsffZNCenQKyDqFAyufTJLiFoBGu" + ], + "birth_scan_exhausted": false + }, + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn": { + "address": "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5289 + }, + "first_seen": 1702336727, + "birth_edge": { + "sink": "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 4372981634, + "signature": "5KeWCHfUdwtMws4e48ZDAqSa2K6bFWeiX4irurLzrFw1geWqtgjHvY1F8T8Cma4n6XuaJ3TbQBaKFZjeLGEK6Wau", + "slot": 235369057, + "block_time": 1702336727, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [ + "EKnwZFKwiRRt5smKp4niv2mi9orJswAAzEdyWto1YFSH" + ], + "birth_scan_exhausted": false + }, + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM": { + "address": "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 157 + }, + "first_seen": 1780522066, + "birth_edge": { + "sink": "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM", + "sources": [ + "6n5o3qNBAjqVjSGZ8qDWoWAHZdxWKpENVh4sJMLzmwL9" + ], + "value": 440000000, + "signature": "5JpzkAF9YrtSadSe8BhHaMYVFBBiugjYzeNr6qEYenDvBzjpckr5s5LQ1zKeGb1WC3ZbVmgWtQypaHJS4pcQLYEo", + "slot": 424109020, + "block_time": 1780522497, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7rtiKSUDLBm59b1SBmD9oajcP8xE64vAGSMbAN5CXy1q", + "6n5o3qNBAjqVjSGZ8qDWoWAHZdxWKpENVh4sJMLzmwL9" + ], + "funded": [ + "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW" + ], + "birth_scan_exhausted": false + }, + "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx": { + "address": "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1150 + }, + "first_seen": 1737448881, + "birth_edge": { + "sink": "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx", + "sources": [ + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B" + ], + "value": 672745485, + "signature": "64C1Msn6wiLA7U5x5ZjYhPx2cj813py9Pft9Bc41VYtbtWHinBJefXt3rj5KaWTuvXC7aoYwr3Y8tsfj1zed1ZSw", + "slot": 315391758, + "block_time": 1737448881, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ": { + "address": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 58 + }, + "first_seen": 1783645887, + "birth_edge": { + "sink": "F5tKdkn183iM3f7Xs2FjS8JYFDkmrJEns1AFoT2TmupZ", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 14433952357, + "signature": "4RbXiaGAmS7WfUmruWn3zBUTwaF2WzVDno2bSTiZFH7cbYSeVeUGgS184pvQaV3ySoRBNGkx6r68M8r5AA5ZbJTo", + "slot": 431912014, + "block_time": 1783645887, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp": { + "address": "F7VxQHbhWgwkCLEt1zwsfHdLT6jaZdaw23szSZQ97NVp", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe": { + "address": "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1785035302, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w", + "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ" + ], + "birth_scan_exhausted": false + }, + "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL": { + "address": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 89 + }, + "first_seen": 1784429559, + "birth_edge": { + "sink": "F8FiqBchYuGpuThChPB22AJmsVcT5Aatko2MQCutfjjL", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 1987000000, + "signature": "22tiKy2Uhr5HbzaoUTkf5gTS2VHov1xMY998kBCQbYybACx6Y3fJtxRBaj8ppaMyahAGEgnC5SggnKt6HbxcwNRb", + "slot": 433813000, + "block_time": 1784429559, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B": { + "address": "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 145 + }, + "first_seen": 1769758558, + "birth_edge": { + "sink": "FBSVzSJA9BpRPmtvsv5qVtHwBGkFsevvmn1Evtggo56B", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 193300000, + "signature": "52ygQjkxX6CiTWcaT8KoncyGrbR97uAqzrJ91LySCtkZDCNDa3WTHpnvNSJkRXacynWy4D5j16sFCh1PWGdesPET", + "slot": 396868730, + "block_time": 1769758558, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt": { + "address": "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11 + }, + "first_seen": 1784509948, + "birth_edge": { + "sink": "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt", + "sources": [ + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe" + ], + "value": 14000000000, + "signature": "55y1RvP9p644eWZy6JypDF39fB4u7XCBXYmQhjDRX5AgytWCpsC2J4SiffzCo9gLPHpPoqs6AXrCXyb7mgUooZXD", + "slot": 434004156, + "block_time": 1784509948, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm": { + "address": "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894210, + "birth_edge": { + "sink": "FH7ffT537ehTR2Da9kejMM78w6dosdq1ZvnhUBJBnyxm", + "sources": [ + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94" + ], + "value": 849104919, + "signature": "2ga76DPJVD9CWDYmtpb17BURi9EcLNFxeNFi172x5aRN9es1qAh5HRHf9ZNCzGtADfp9LzzcF2t26MdDn5jEDT5C", + "slot": 394707744, + "block_time": 1768894210, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94" + ], + "funded": [ + "9ufYR8kSVuEhQav6YV8ZNjt4fXiLzHH9TJZ9G7fCQtQh" + ], + "birth_scan_exhausted": false + }, + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor": { + "address": "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 856 + }, + "first_seen": 1783108022, + "birth_edge": { + "sink": "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor", + "sources": [ + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu" + ], + "value": 200000000, + "signature": "3XBCwvm2pUrPEp3PCUKm4NiNy7pGhpxqpy2u2eWnMBs5sJgoYk55DPp77RSEn4m2vL9C61sWiefBAiArFGEaUgFw", + "slot": 430586035, + "block_time": 1783108022, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3i7rnk82B2fZXgQRrx2visk2D919YTdEwZqX22opdBfu" + ], + "funded": [ + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe" + ], + "birth_scan_exhausted": false + }, + "FWKrAi8ZDaeKx9NQoJmLGyMoujLUUjsM8pqmghFqrtaf": { + "address": "FWKrAi8ZDaeKx9NQoJmLGyMoujLUUjsM8pqmghFqrtaf", + "owner": null, + "executable": false, + "signatures": { + "Exact": 996 + }, + "first_seen": 1752221038, + "birth_edge": { + "sink": "FWKrAi8ZDaeKx9NQoJmLGyMoujLUUjsM8pqmghFqrtaf", + "sources": [ + "fkY8mx6y5VYYsybgy3q2A5A8ZphJewgmBBhx4ZsEAQf" + ], + "value": 3010000000, + "signature": "4iJnDGLdugCoiXCAoSMZ7FzxRcXA6T6WCNK8harksPvwxFUWU3BM4B4KnQNarzjcRVdRuNw5iytvfaSwTzf6wQmG", + "slot": 352550106, + "block_time": 1752221038, + "ambiguous_attribution": false + }, + "fee_payers": [ + "fkY8mx6y5VYYsybgy3q2A5A8ZphJewgmBBhx4ZsEAQf" + ], + "funded": [ + "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r" + ], + "birth_scan_exhausted": false + }, + "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd": { + "address": "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 48 + }, + "first_seen": 1765645486, + "birth_edge": { + "sink": "Fb9kYXyPX2pdkzn7RtscvHv4obLMZq1LjFCWasQnedMd", + "sources": [ + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC" + ], + "value": 88200000, + "signature": "sH5bnyJn2BNFN3TgD2isA2G5ygXEsGzWr9kpgN2PW5Ubxk3nAHyLRprFmS5M2FzdbTTZD2RjxfgH7xfbARZtdHc", + "slot": 386459128, + "block_time": 1765645486, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BDPFerK9hZKUKHLoDYVwUifk9C9BdZQTid8Kdq1ac1eC" + ], + "funded": [ + "5EiNo9MsuuKcVaDEezYsAGdkavoQYSYNRZYH1cnB4RtZ" + ], + "birth_scan_exhausted": false + }, + "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM": { + "address": "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12 + }, + "first_seen": 1784461923, + "birth_edge": { + "sink": "FbSXcvGRhDdct2URYZkRZLYyaCbXNkRib6SbUzazTmAM", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 990500000, + "signature": "9duBpzRRMAZ2cQv2n79HKMzurHUBWmDWdDwNR8y6U48bnG6HKY2HZnrQxXNFVFpg8RtSy3KPNEY4ByMKHEBJCEb", + "slot": 433890247, + "block_time": 1784461923, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ": { + "address": "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1670 + }, + "first_seen": 1784580105, + "birth_edge": { + "sink": "FcFhk1BshcZPmR7EU4cX9YFXKN9juvy9WNvaoHMRf6WQ", + "sources": [ + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm" + ], + "value": 49209767318, + "signature": "2BVoGzFZrFnuRLqpW1EAHxkFXmnp7xWp1wog4n3EBnCbTgQLvSgFVZHB8z4Z1FgdzjYGBh9tTUFZ1aLF2XtYCuKU", + "slot": 434168198, + "block_time": 1784580105, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BLCkSaMKEPLaX1yvHiymRhSAF52mc5RMuUL1KpuxMPtm" + ], + "funded": [ + "31r21jLmyUXEyPdGCEQ3gqj66XcWLjvAqVcJttZHNxyr" + ], + "birth_scan_exhausted": false + }, + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN": { + "address": "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18203 + }, + "first_seen": 1712861701, + "birth_edge": { + "sink": "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN", + "sources": [ + "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J" + ], + "value": 100000000, + "signature": "2YSSsjdWzcSHyrYYRJy7C6JT3pT8iZQGVbJPMpPVCGzKk48L3CWwuqTx9fMkmrYstCeQVX4p77wz4HmL2qyMHYVK", + "slot": 259584539, + "block_time": 1712861765, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J" + ], + "funded": [ + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe" + ], + "birth_scan_exhausted": false + }, + "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7": { + "address": "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 48 + }, + "first_seen": 1784809669, + "birth_edge": { + "sink": "Fm5haWEgwGveZU777yCoQRyJQ2L39qXQnMhC4FcRVYE7", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 5973000000, + "signature": "3MSs2ea12TniywAz7iM9anGzcgJr8oB1kCVxVQm8YgQxqoCwwPpzBhg6EACKhpDuWoT6J723YZfrnDq8qqfLSTRJ", + "slot": 434715093, + "block_time": 1784809669, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "31kvzxJYHVUPBNqmzjLGBWHK6SiEjhVYGThEiovi9Bf1" + ], + "birth_scan_exhausted": false + }, + "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k": { + "address": "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 955 + }, + "first_seen": 1781405876, + "birth_edge": { + "sink": "FmvvAVe9CN4GAXuQ9LmvceH4VqmGLmr79KidiMtSy51k", + "sources": [ + "9HGyyJnWK4b67E97bB4SXpjbxpYmTBMaKNmWmfmYe4DK" + ], + "value": 55000000000, + "signature": "3NpN3bBydLQE7k375NJpiQnNumbTbwDBmNSZ3yjFR9UrXhkjoWQu93SAwyL3oBQjwq3Ngmmc9BW4xQJduKX3Z3sL", + "slot": 426330281, + "block_time": 1781405886, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9HGyyJnWK4b67E97bB4SXpjbxpYmTBMaKNmWmfmYe4DK" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B": { + "address": "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B", + "owner": null, + "executable": false, + "signatures": { + "Exact": 15 + }, + "first_seen": 1737447329, + "birth_edge": { + "sink": "Fr4tRypWcy46S5VX4wVENtEPagwPiC2REPxVquGXJ66B", + "sources": [ + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "value": 672750485, + "signature": "4gBVShePEtPdc8c9vLekxqxtKnHN1CaY2ECsfrU3Ysoo7AeQGVwwpqLDW9aAWYF3KSsekeoaA1aqHrTqAEHwSGuN", + "slot": 315387958, + "block_time": 1737447329, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "funded": [ + "F3ZNeEia1bFKjwL4KcPYfkRsVu5MRiqBz48sThhR34gx" + ], + "birth_scan_exhausted": false + }, + "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv": { + "address": "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1774644756, + "birth_edge": { + "sink": "Fs82mFGwmL36XREhYgQGpmb4JDiGWDdXm8ALTP3R7wwv", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 5973000000, + "signature": "5fTttEt3Pb3F4Sj5dKKrkqFCZEJqR37qyLLYVSA7GwBqbyn4MCv2LDEZBKuh9pGRhdxTJa7xNMKH6c53gXrno7RZ", + "slot": 409284059, + "block_time": 1774644756, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [ + "6oABnktxmqBX7S3aF64nzD3zYj7Dt6bjYDt8xTwYj6oY" + ], + "birth_scan_exhausted": false + }, + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF": { + "address": "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF", + "owner": null, + "executable": false, + "signatures": { + "Exact": 253 + }, + "first_seen": 1730807547, + "birth_edge": { + "sink": "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF", + "sources": [ + "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP" + ], + "value": 704147548, + "signature": "5KG9RmaRtZiEhhjCLK3qbwhczgeFao4cCPCNFGAt9ZmyUeAqK3Rviq2g6QCrd5maMV1KuoBP1cds2Wr4BgyK77nk", + "slot": 299638304, + "block_time": 1730807663, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP" + ], + "funded": [ + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" + ], + "birth_scan_exhausted": false + }, + "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw": { + "address": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 14493 + }, + "first_seen": 1777091838, + "birth_edge": { + "sink": "FuasKFTCGY2EodUE7vDXkEf6a8TC3gFjCrW6DyLWyxSw", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 18571768434, + "signature": "44VV33bxUiT3rDmG3dtN2uxnDx4uBs6keq7PWCmx3rdqWhvPmPXJhrA7Sq7atABrqAkpaBZH5wdRRWMkFQ7j8kNA", + "slot": 415493545, + "block_time": 1777091838, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "FzZ77TM8Ekcb6gyWPmcT9upWkAZKZc5xrYfuFu7pifPn": { + "address": "FzZ77TM8Ekcb6gyWPmcT9upWkAZKZc5xrYfuFu7pifPn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1777383974, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "C8nKoCYa6vbtv51Gszak8W8UoMzCu32ks56rDYdMpwwr" + ], + "birth_scan_exhausted": false + }, + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs": { + "address": "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7899 + }, + "first_seen": 1731918662, + "birth_edge": { + "sink": "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs", + "sources": [ + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" + ], + "value": 11000000000, + "signature": "4NrJoknq1Jx9gtAQ8bxTuhWoNxMWggLDQS4XBN1zsNRVXGfCvC21JBkKGsxLH9hppZBa6KxaMLve5iiowk75DVLb", + "slot": 302103526, + "block_time": 1731918662, + "ambiguous_attribution": false + }, + "fee_payers": [ + "E3D5kVEJbT2dnRgMknuMvz8n4cq8aT4stZ9YYy593N4K" + ], + "funded": [ + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf" + ], + "birth_scan_exhausted": false + }, + "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G": { + "address": "GB7SkZMTwZVZxdXfrsPpQFNqEWV88fK5jwQHsPKJU28G", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1781111107, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2fbzcJYZDNKURTR65dKBAkVwgnGNDAsbz8ykLAQ8Eapg" + ], + "birth_scan_exhausted": false + }, + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE": { + "address": "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784781784, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "5UkMq28tMYqtH3ybUzKoSXFxBKEYvoDPYLd9EZGEUCFV" + ], + "birth_scan_exhausted": false + }, + "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT": { + "address": "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1784758056, + "birth_edge": { + "sink": "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT", + "sources": [ + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU" + ], + "value": 90000000000, + "signature": "2yFwMANAjHWBoCUWXwsF2NS8Vm24oSkE5oJ6RfsDe2jPVeo782fE98nePAJqpuxWL1VkA5iQXoXdDoU8SqopCTR2", + "slot": 434591833, + "block_time": 1784758056, + "ambiguous_attribution": false + }, + "fee_payers": [ + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq": { + "address": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 65 + }, + "first_seen": 1784659136, + "birth_edge": { + "sink": "GT7mTRv43hRBMQgkofHFfqQkqNoT61W3f3BWt97kqonq", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 32880718309, + "signature": "4wZbNhexVa96s82hAQbdZ9Pq1fqencGxxmKYCwghVjT6iwxMLyyKBssfwvWVJFXFMXxNwSfrDo8TqZ4AiJbEPC6U", + "slot": 434356458, + "block_time": 1784659136, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC": { + "address": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 298 + }, + "first_seen": 1783054209, + "birth_edge": { + "sink": "Gb2HfReRRLpp8w5zkKhu1SSTpkpWbTMdjwduvLiiDivC", + "sources": [ + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx" + ], + "value": 180834295, + "signature": "55tLEDwJyuDGaSo8c6yYz4bj5utz7zJiVtQnsQuwqKg7abdYWVoK1vFkLQd7pyJhjh4r8ku6i9oCyXRa8a6kX18z", + "slot": 430452695, + "block_time": 1783054209, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8d9FNC7AgKLTCPKNd3MMkLLXZYLmiYFYR3vfXMBNJVNx" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf": { + "address": "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894200, + "birth_edge": { + "sink": "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf", + "sources": [ + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ" + ], + "value": 849114919, + "signature": "5Gb3um1roTYvbjCFckDeFVPkU7QxKeShYSL3KWDxSEX8qhCrwgDEJgbSiuL3L4tCXzk1GaguKXBbmAU2yjAVmkdG", + "slot": 394707716, + "block_time": 1768894200, + "ambiguous_attribution": false + }, + "fee_payers": [ + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ" + ], + "funded": [ + "CmmQ3kMvdAmC6sgpQordubMP71ExikgCduFPbjeW9B94" + ], + "birth_scan_exhausted": false + }, + "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk": { + "address": "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1154 + }, + "first_seen": 1777988933, + "birth_edge": { + "sink": "GkRhAmsDpHq6C2BaMRv3y8Q6X9vDX5ffohHtdndvqNk", + "sources": [ + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" + ], + "value": 2362855000, + "signature": "Vdct3Y5hjgKy2gHHjimC9YE8faisFo7jz3EiFYjmb3yAYuHpY2tE5ZqJcXhMNtbrrXTRr5CxYpCz5djyYzwgJyb", + "slot": 417761237, + "block_time": 1777988933, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5ndLnEYqSFiA5yUFHo6LVZ1eWc6Rhh11K5CfJNkoHEPs" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho": { + "address": "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho", + "owner": null, + "executable": false, + "signatures": { + "Exact": 600 + }, + "first_seen": 1784462427, + "birth_edge": { + "sink": "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho", + "sources": [ + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva" + ], + "value": 51022180729, + "signature": "4udxkWqxkyLqBAZpUJHNMkZDcFMVTq1BpaC2A5YqNGbm6He6WAYpQGQXMY5J91YcUxxZATqVkJooVP9y3EXqpABd", + "slot": 433891452, + "block_time": 1784462427, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva" + ], + "funded": [ + "7u9DY46GaThvrRxCnaPESd2Q9NXvsApEpJ4ntF47A7hP" + ], + "birth_scan_exhausted": false + }, + "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp": { + "address": "GsZHxEz792ibtNt6QzxbuFwQkEzvoo7HBsETSLbtgXcp", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J": { + "address": "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3385 + }, + "first_seen": 1634119967, + "birth_edge": { + "sink": "Gvy8F7L7JjaiFSmNpgCjpDrArXbtRQkRKwijdLRdEv6J", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 310000000, + "signature": "5sRWZE4rpYG3LpEb1rGQKy7x2CJry6y2ekJukNkFE3NpgSX5PqN24oyoyJE5tDw6nrmcQkeAvcw961G3yUdYt2DA", + "slot": 102567198, + "block_time": 1634740692, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9JqkbkGecn7FtryKweonAT445Y8KaLaEQhutxPN7d7xN", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [ + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + ], + "birth_scan_exhausted": false + }, + "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP": { + "address": "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP", + "owner": null, + "executable": false, + "signatures": { + "Exact": 105 + }, + "first_seen": 1671008446, + "birth_edge": { + "sink": "GyCwk4A6Hbszbw7H1VH4DZhjugmZRgAqAf5Gmcik27mP", + "sources": [ + "9Kjd4PMdZFQrzuGvv7PuMA1uXTu433GP5CqHGNaJhtja" + ], + "value": 750000000, + "signature": "3MPx7uhsdKLSamxE4Q5fGaK2ycW3DzFaMhLk92XmjhTa94bEE8Peg1DnebE8Edk4K3dGQw42DmJ9eQaYZsCJgg18", + "slot": 166798101, + "block_time": 1671008446, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9Kjd4PMdZFQrzuGvv7PuMA1uXTu433GP5CqHGNaJhtja" + ], + "funded": [ + "FtuidQt6GZ2L9y67rHMqskd4DceLDj2W9WpWMWvLSRxF" + ], + "birth_scan_exhausted": false + }, + "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF": { + "address": "H2PVM96jQomH864kZAAxRMFBZaHbdKQG2iG21v52jRxF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1744045119, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2ND3iffXP4SQ5NoivmjmeuNXe5LjFUjQuVwtPWS7CB2X" + ], + "birth_scan_exhausted": false + }, + "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva": { + "address": "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1629 + }, + "first_seen": 1784212802, + "birth_edge": { + "sink": "H5A9P6X933RbVuqaohgrPpuY9KhpSnoT87LB5CbHKeva", + "sources": [ + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE" + ], + "value": 22180583705, + "signature": "59BrgexB78Kj9fCJkHmKs2UZYsp5Ntc5ia4RpE3T1WS8xtwyJEhn49w5VcofBvhkHWa9AxfEQsR1L6qjNFKdZnhj", + "slot": 433291830, + "block_time": 1784212802, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BYMyvenA3hVcNLKkYLpx2pLeSgFDrfzaq9CD1TnBebGE" + ], + "funded": [ + "GsTWfuRzhBDnjKeHjLYW7q6DKFgordHnGUCCLtEc8zho" + ], + "birth_scan_exhausted": false + }, + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS": { + "address": "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784779719, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "BMDvnhyfuNQpJNRrrDQ3yC8nR33YUfwd5hsB17HCfy7m", + "En47we6eRGG3owJvGs2v2rsHZLVuvAC1cksEMqCSQFDn" + ], + "birth_scan_exhausted": false + }, + "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ": { + "address": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20062 + }, + "first_seen": 1780029667, + "birth_edge": { + "sink": "HDz7F7kFk12bKxJVSZnJbdVnCySpQ5AFU9HBnn9N3HYQ", + "sources": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW", + "4AV2Qzp3N4c9RfzyEbNZs2wqWfW4EwKnnxFAZCndvfGh" + ], + "value": 2442413156, + "signature": "2UXxvAd3GMF7t1ZMWhrXJcFP3q6iYYcZBEMeynG6PWWz8vrttyjdwXo1KSxrYhGU8zchaaM6979gnU1mz4d3dHKr", + "slot": 422867536, + "block_time": 1780029667, + "ambiguous_attribution": true + }, + "fee_payers": [ + "AF8VuwCncKd5ZBnLYYnMjqh4vLch8mjqE75sFe5ZjRFW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe": { + "address": "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 737 + }, + "first_seen": 1784227892, + "birth_edge": { + "sink": "HHVTQsCM8Dy5MGeJxpagzp1KqWXEQqS1oz2DAETkMvwe", + "sources": [ + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor" + ], + "value": 130000000, + "signature": "kTyjASGv9R4C1zjLJriyX96k2ZWgbAwPw3Qd2z3pCzb5cRPuZs3vt8uqzKH2bjr5AdqMDwf8SnxCqbwyt2cWwdm", + "slot": 433328336, + "block_time": 1784227892, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FJapDp832EnGSyyi5WiJCzLnVd7n48CVgrW5QvvH4Hor" + ], + "funded": [ + "FEkoSkBGCqgiduibgunL1VVL7x2cEFBat5dEWwoTbTTt" + ], + "birth_scan_exhausted": false + }, + "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq": { + "address": "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 14 + }, + "first_seen": 1784552253, + "birth_edge": { + "sink": "HQwxajypJoUshau4FqqeFBHPBJfUodsDgdb3Dtgb8uSq", + "sources": [ + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW" + ], + "value": 23698818508, + "signature": "3uywYCF5VuSzVQkMX6Pghx7SXTsJuW55Jv37zcHt1foh71Nuq6vGrb4keCz7JNFjLMRWkK5Aq2jNmWhqkWbXuH8y", + "slot": 434103135, + "block_time": 1784552253, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AbPrRZG6CDhcd48XriyvDwMdrJobk4oSZSbRK1dyYEiW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC": { + "address": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4402 + }, + "first_seen": 1776709519, + "birth_edge": { + "sink": "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 4909585890, + "signature": "5VEpmW6pcMbLtJQHaJbctF8BUkixM7cH6gwV5yMHLtJtq5DaCpFaKRydWVjQ85d3uA9ErzyjYTkqyfHriUeN1D8E", + "slot": 414528392, + "block_time": 1776709519, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165": { + "address": "HVZXVsbsnvNJuHj5xBCUppm2c5i2gVuryFGMRpFRu165", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1776477779, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9": { + "address": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 41 + }, + "first_seen": 1784377766, + "birth_edge": { + "sink": "Hi9cLJjbBSpk7VbRSqich9TCiWYMxuyrWNcqgmWNprr9", + "sources": [ + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN" + ], + "value": 1299788806, + "signature": "DonAnKr8whXoDnWemm9bqSjoUQdEohd9sus4nhstSat4cF5HxAmnrHQqMEbbNW3s9KhRDYLvL28ifTpBj16tWha", + "slot": 433689104, + "block_time": 1784377766, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CkLTyEQW3xUxbb1z11zj9PwnYAPiLScLaeVrNwxmeDtN" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k": { + "address": "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 112 + }, + "first_seen": 1783337142, + "birth_edge": { + "sink": "HnTGgj9MgsHVEMNWfs2RQsDzQsaZLC9Ytbno7L6Q9u9k", + "sources": [ + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w" + ], + "value": 2000000000, + "signature": "3TYxeVhY5FHcpE3BqdMtf6RUScLkjcsXBpZnPjc8YfGiTXw25vLWWvd3vZWPE9V6kBcLpzMrph82uRoprzHPNvKU", + "slot": 431153926, + "block_time": 1783337142, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4prmwkseFzz51H6Fb1AAsuispLPABrbqoyyK7mjBDM3w" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v": { + "address": "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 8839 + }, + "first_seen": 1769181912, + "birth_edge": { + "sink": "J1KXUDXsJ4voYtNKoQqDhwZsYJt72NmB1JfcQKJ1vK5v", + "sources": [ + "7xcCLxFxNxGdj8XPTveMh7gZ8vhEowe6Si9QpoFuCZai", + "44GW6aFire4Fd72h4QqjenNKrQLjfnApHhWYXo3S1gvp" + ], + "value": 2079130000, + "signature": "59iZNmvqD8XUZ7fth4tAHjCaoQMcBHFdeTFmXSvHD6kVmFTxzeAfRdgQu3njeW9Lew3EgoJv3DNwrcKw2QUco7MZ", + "slot": 395430963, + "block_time": 1769182419, + "ambiguous_attribution": true + }, + "fee_payers": [ + "BidtDwH9WbEvm9T6tqqsEgAscTzLJeX6H2rGbw22jR39", + "7xcCLxFxNxGdj8XPTveMh7gZ8vhEowe6Si9QpoFuCZai" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ": { + "address": "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 51 + }, + "first_seen": 1783637441, + "birth_edge": { + "sink": "JDmzz2bGQwjPAN5gkEDCKd9E4zdWVFHLDenY9NmrJQaQ", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 22952461058, + "signature": "63UUpBFp2VbRkpjKsc3FaXXucqtzjTyPZCsqEtoXBxNiW263FxXHdr6Coo7FksLHC3amGnSZoV9rD9uR4G4yKMfo", + "slot": 431891370, + "block_time": 1783637441, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE": { + "address": "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3053 + }, + "first_seen": 1761711549, + "birth_edge": { + "sink": "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE", + "sources": [ + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" + ], + "value": 10000000, + "signature": "42BSukNNZwdKRuESCNwP7Dr8xn7HmRc21PNP6PdSa36WrF1mbw2n3CatJwkBzviQiutmRPDn9oCR7bJo4jXDVtzM", + "slot": 376499867, + "block_time": 1761711549, + "ambiguous_attribution": false + }, + "fee_payers": [ + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ": { + "address": "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1768894187, + "birth_edge": { + "sink": "eESjJDBAvxZkEARQAnTcPiiif3NsxKdQSyy5LdSJmvZ", + "sources": [ + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x" + ], + "value": 849119919, + "signature": "5hopWZ3eVdGLYmfhGBcqbvTV2SGzmQc8X3KyyfBQ36QzCcyGtMkxBKhTLuYwftq866p9ZdaZFVMbrsRNNGjzE3Cm", + "slot": 394707684, + "block_time": 1768894187, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7Jx48Zct3x8Y3qikV6LXHZx4572pMoCY4VkPGDrXA86x" + ], + "funded": [ + "Gfw9RerLqaQgHYoxCDXSF1xBSWyscZogeKisEPNPhSXf" + ], + "birth_scan_exhausted": false + }, + "fkY8mx6y5VYYsybgy3q2A5A8ZphJewgmBBhx4ZsEAQf": { + "address": "fkY8mx6y5VYYsybgy3q2A5A8ZphJewgmBBhx4ZsEAQf", + "owner": null, + "executable": false, + "signatures": { + "Exact": 171 + }, + "first_seen": 1751927442, + "birth_edge": { + "sink": "fkY8mx6y5VYYsybgy3q2A5A8ZphJewgmBBhx4ZsEAQf", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 60735245700, + "signature": "SX1PR96BYugSoJvSnb2V1NN4kaC4wFtc4tt9RLpMa63gmdV38vfJuejYgMXnByaFg3qa8T1GAinuq8f7E69BsW4", + "slot": 351809035, + "block_time": 1751927442, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "FWKrAi8ZDaeKx9NQoJmLGyMoujLUUjsM8pqmghFqrtaf" + ], + "birth_scan_exhausted": false + }, + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu": { + "address": "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784720951, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "HVDyeC7kC3iCqf5TEncLk5b2rKcqfXtBBhSqnLmQeXZC", + "1kkC2ZFRxpfkYbMCp3NFbPsaUoTmkdGaTBSwxweKC1E", + "9i1et2MVHNkMp7RLVCM3gBizxE4PoJkgbt7MRk2Q9Fdn" + ], + "birth_scan_exhausted": false + }, + "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW": { + "address": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 108 + }, + "first_seen": 1784490766, + "birth_edge": { + "sink": "iXQYQbH37U7YnvcmzwrCYm19BK6i1Zn8NYmD6J24JCW", + "sources": [ + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM" + ], + "value": 150000000, + "signature": "2uKVQ2Do2Vandv4rqHhJQZEFKrKQGFQdt15BtdaknzdY8Rxas2Bx3smqkhK6D3Hz8urRCANByPKEJxDcCAKTaq3G", + "slot": 433959006, + "block_time": 1784490766, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EoTqf7MM4NYyiFZD2idh6uy3eY3TbgEokpsV3HR6zPTM" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD": { + "address": "inWVrrYJ38VihdE62LXNQvgV5CeRrdKEXpNtXLyqUWD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1785032587, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "4jewC6oZ3gN5HfU2eDzVmsQLQrRU86gEwV66BeyRc7pT" + ], + "birth_scan_exhausted": false + }, + "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU": { + "address": "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 583 + }, + "first_seen": 1769032382, + "birth_edge": { + "sink": "k1FM496vdPWxeNMigjNBxjTZ4U8S8Ut67o9bAGuPVgU", + "sources": [ + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi" + ], + "value": 20000000, + "signature": "AG1dX11PZCL7yBeTVs4PuNNECWsZ7tuvE94MP16zRMdtgyqchb5gyD512vVb7bd5pk3rYHpfg1RFFCwSLaahc4w", + "slot": 395055111, + "block_time": 1769032382, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9yzDcVC569tYadQqmuNPGNNa3ZmHA6AY7tdgWnSEvDbi" + ], + "funded": [ + "GJXuPrtuANNJ3xrSVQNQWwSHUjduSscwyKjqxD8jt4FT" + ], + "birth_scan_exhausted": false + }, + "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q": { + "address": "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q", + "owner": null, + "executable": false, + "signatures": { + "Exact": 9987 + }, + "first_seen": 1753767232, + "birth_edge": { + "sink": "mmekwNde1Yh43CqecqAh6DW7GyvJvz4wjxDu3Kr9Q2q", + "sources": [ + "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r" + ], + "value": 15080000000, + "signature": "4jUBNeaABoUS9e3JfuUfCdZSKJDoT21rHND3BBd9B59kmgbT2HM6EpRB3kVmgB1eRoqv1nnV8GruBtB5gZ84hd8D", + "slot": 356448864, + "block_time": 1753767260, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8d9UB1epGA22yxoRVxoe86njAwmA7a6AxW96mc9v5n6r" + ], + "funded": [ + "LPsefXkaaAqLMoEK3HhJWRhd6qk3xzeTUgmtQ727YEE" + ], + "birth_scan_exhausted": false + }, + "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf": { + "address": "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2651 + }, + "first_seen": 1737914051, + "birth_edge": { + "sink": "pJ8AaFpuMGoHhcrT6iJuTvdrLUcuUPxAP6SSwzWkArf", + "sources": [ + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" + ], + "value": 50000000000, + "signature": "2rpBPJocSrWKt2G59e29e8Tj7MJDUAQLEKx2GaoAApyUBLbynToft6iDKYZKTWr2BL9UhfiwV7qr6E1exQn6mnAP", + "slot": 316536578, + "block_time": 1737914051, + "ambiguous_attribution": false + }, + "fee_payers": [ + "G4SZFKHdiNGsFmYHzJt79xMqcS6UmkGAXwoQtstjyfVs" + ], + "funded": [ + "DGH8fu3Msz8u41o8VintuvcFSb3AXW47yw2gyvMf7YJi" + ], + "birth_scan_exhausted": false + }, + "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe": { + "address": "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2006 + }, + "first_seen": 1762263640, + "birth_edge": { + "sink": "qNvqCj1bm5QU6mS2az6CuRx59jTpt5Bz48LvCrwVtZe", + "sources": [ + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + ], + "value": 10000000, + "signature": "2abx2yUwotMytWNz2EAiW6RTrAsz9otzWMXKPyVN8FjtaEa3hp4MTJYi2C3R8XrnUND8MyuMJ5T2yKEb4prkrBVJ", + "slot": 377877154, + "block_time": 1762263640, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FfTmAnvfoH1Jcki3t9xtknkbAneT11tnDgR64TgsrYXN" + ], + "funded": [ + "CrAWByBbF5qNRLS67cshtjenGvRmgSLCmAno9GRJqLqz" + ], + "birth_scan_exhausted": false + }, + "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6": { + "address": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 531 + }, + "first_seen": 1783364451, + "birth_edge": { + "sink": "xukyMmDrvUreyVX2stb5DyFD7MopGzYfTisLFwg6Tf6", + "sources": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + ], + "value": 1100000000, + "signature": "4kLBV2LbExCtK85mRP4FnNGDaHVTBNNvvtyQjbFVqiqeLgvs3fcSUVbsxtmuZ6WzVUBYV9h9GJzyUuWpYf6nAxTD", + "slot": 431221166, + "block_time": 1783364451, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8hKxzjzh34MNV9WWvQs2XY98aNRSdi7TUPU2LCNDZjk1" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx": { + "address": "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 74 + }, + "first_seen": 1776857831, + "birth_edge": { + "sink": "zd5ndEyiKnvJcZg3SkS9z98uDuUbRmJJuEvn3rQYDmx", + "sources": [ + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" + ], + "value": 1000000000, + "signature": "4mEmeFnTKjqyT3XM3UZbp7fGA5gWeMB8ny87kZJbBbGGW89NJv5i5GBSvAJqx4JQN4eKDKNUomka7gE4AvgLRm8v", + "slot": 414902368, + "block_time": 1776857831, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BME6PqbfxUx2Ff4WzjE5AAboFy6AUFo6L9MmARWPzf3K" + ], + "funded": [ + "E96zNVAM8yq3wLiU6FKbgyPASEVtg4QGRrmpykGUn9Do" + ], + "birth_scan_exhausted": false + } + } +} \ No newline at end of file diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index 4655c49f..8733cb3b 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -332,6 +332,52 @@ already published, and including the comparison failing to separate. size-biased frame, one above the RPC-failure limit. Neither's conclusion depends on the birth-edge rule, and both remain in this document as they were. +### Run 6 — the privacy pool, re-collected + +Helius, archival probes passed. 1,776 calls, 364 seconds. + +``` +attempted 83 | resolved 54 | evidence-unresolved 0 | budget-unresolved 29 + | scope-unresolved 0 | rpc failures 0 (0.00%) +``` + +| quantity | Run 4 (broken) | Run 6 (fixed) | +|---|---|---| +| resolved | 50 | **54** | +| **evidence-unresolved** | 6 | **0** | +| budget-unresolved | 28 | 29 | +| **ρ (point)** | 0.1032 | **0.0955** | +| ρ (unresolved bracket) | 0.0316 … 0.1318 | **0.0350 … 0.1136** | +| bracket width | 4.2× | **3.2×** | +| ρ (95% sampling interval) | — | 0.0848 … 0.1790 | +| classes observed | 22 | 23 | +| Good–Turing coverage | 0.62 | 0.65 | + +**The evidence bucket went to zero, and that is the finding.** Chain stop reasons +before and after: + +| stop reason | Run 4 | Run 6 | +|---|---|---| +| `PageCapHit` | 69 | 76 | +| `NoIncomingEdge` | **10** | **0** | +| `LocalTerminal` | 4 | 5 | +| `BirthScanExhausted` | — | 1 | +| `DepthExceeded` | 1 | 1 | + +Ten addresses were previously reported as having **no funding credit at all** — a +claim about the chain, not about us — and every one of them was wrong. One +address now honestly reports that we stopped reading before finding its credit, +which is a claim about us and is counted as budget. + +**The corrected headline is ρ = 0.0955**, lower than the number published before, +and the bracket is a third narrower. The correction happened to move our own +figure in the flattering direction; it was made because the code disagreed with +its own documentation, and the direction was not known until the run finished. + +The under-sampling flag still fires, and Run 4's finding about the tail survives +the fix: coverage remains near 0.65 with Chao1 estimating 108 classes against 23 +observed. + ## What we do not conclude **Nothing about how private that pool is, and the headline does not become that From 420b281cd995af04929275013403e863114f483e Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 02:34:38 -0300 Subject: [PATCH 45/83] measure: the control run, the refused comparison, and the gate we added to refuse it Run 7 re-collects the control with the fixed tracer. The bug bit it more than twice as hard as it bit the privacy pool: 36 of 92 members had been reported as having no funding credit at all, and every one was wrong. Resolution went from 16 to 38. It still refuses. 38 of 92 is under the informativeness gate's half. The comparison is where this gets uncomfortable and is written down as such. The interval on the difference excludes zero and points the way this project's argument wants: the privacy pool reads as the more concentrated population. compare refuses to report it, because the control resolves fewer than half its members and what remains is its traceable subset -- and traceability is not independent of provenance class, so the difference can be manufactured by selection alone. That gate did not exist when compare was written. It checked the RPC-failure gate and not the informativeness one, an inconsistency between two commands that should apply the same rule. It was added after seeing the control fall on the wrong side of it, which means we built the check that refuses our own favourable result after learning the result was favourable. The log says that in those words rather than presenting the gate as foresight. Also corrects a false invariant asserted in the bootstrap tests: that the resampled range always contains the point estimate. It does not. Under a heavy tail, resampling drops singleton classes about 37% of the time, which raises rho in nearly every replicate and lifts the whole range above the estimate. The old test passed only because its fixture had a mild tail. The type is now documented as the spread of the resampled estimator rather than a confidence interval, the bias is reported beside it, and the heavy-tail case is pinned by a test. 193 tests. --- crates/mirror-cli/src/main.rs | 98 +- crates/mirror-provenance/src/bootstrap.rs | 97 +- data/sample-marinade-run7.json | 5302 +++++++++++++++++++++ docs/MEASUREMENT_LOG.md | 77 + 4 files changed, 5547 insertions(+), 27 deletions(-) create mode 100644 data/sample-marinade-run7.json diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 3f76da1c..81666b63 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -190,7 +190,7 @@ fn vk_digest(vk: &mirror_circuit::SolanaVerifyingKey) -> String { /// comparing endpoints rather than populations, and it would do so invisibly — /// the difference between two pools and the difference between two collection /// runs look identical in the output. -fn resolved_labels(path: &std::path::Path) -> Result> { +fn resolved_labels(path: &std::path::Path) -> Result<(Vec, u64)> { let text = std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?; let sample = mirror_provenance::Sample::from_json(&text)?; @@ -208,10 +208,13 @@ fn resolved_labels(path: &std::path::Path) -> Result> { census.failure_rate() * 100.0 ); } - Ok(results + let labels: Vec = results .iter() .filter_map(|(_, o)| o.label().map(|s| s.to_string())) - .collect()) + .collect(); + // Unresolved excluding our own RPC failures, which belong to neither side. + let unresolved = census.measurable() - census.resolved; + Ok((labels, unresolved)) } fn main() -> Result<()> { @@ -517,14 +520,30 @@ fn main() -> Result<()> { ) { println!(); println!( - "rho sampling interval {:.4} .. {:.4} (95%, {} bootstrap replicates \ - over the members drawn)", + "rho under resampling {:.4} .. {:.4} (2.5-97.5%, {} replicates)", i.lo, i.hi, i.replicates ); + println!( + " resampling bias {:+.4} (mean {:.4} against a point estimate \ + of {:.4})", + i.resampling_bias(), + i.mean, + i.point + ); + if !i.contains_point() { + println!( + " The range does not contain the point estimate, and that is a \ + property of this\n population rather than an error. Resampling \ + leaves about 37% of members unpicked,\n so single-member classes \ + vanish from most replicates; fewer classes means lower\n H(C) \ + and therefore higher rho. The size of that gap is a tail \ + diagnostic." + ); + } println!( " This is the spread of the estimator, not its distance from the \ truth. Plug-in\n entropy is biased low at small n, so rho is biased \ - HIGH: the real loss factor is\n plausibly below this interval, and \ + HIGH: the real loss factor is\n plausibly below all of this, and \ equally so for any population measured this way." ); } @@ -583,8 +602,8 @@ fn main() -> Result<()> { label, against_label, } => { - let a = resolved_labels(&sample)?; - let b = resolved_labels(&against)?; + let (a, a_unresolved) = resolved_labels(&sample)?; + let (b, b_unresolved) = resolved_labels(&against)?; let reps = mirror_provenance::bootstrap::DEFAULT_REPLICATES; let seed = mirror_provenance::bootstrap::DEFAULT_SEED; @@ -595,21 +614,20 @@ fn main() -> Result<()> { .ok_or_else(|| anyhow::anyhow!("{against_label}: no member resolved to a class"))?; let width = label.len().max(against_label.len()).max(10); - println!("{:7} {:.4} {:.4} .. {:.4}", - a.len(), - ia.point, - ia.lo, - ia.hi - ); println!( - "{against_label:7} {:.4} {:.4} .. {:.4}", - b.len(), - ib.point, - ib.lo, - ib.hi + "{:7} {:.4} {:.4} .. {:.4} {:+.4}", + labels.len(), + i.point, + i.lo, + i.hi, + i.resampling_bias() + ); + } let d = mirror_provenance::difference_interval(&a, &b, reps, seed) .ok_or_else(|| anyhow::anyhow!("nothing to compare"))?; @@ -620,6 +638,44 @@ fn main() -> Result<()> { ); println!(); + // The same informativeness gate `analyze` applies to a single + // headline, applied to each side. It matters *more* here, not less. + // + // Comparing two populations of which one is mostly unresolved is + // comparing their traceable subsets, and traceability is not + // independent of provenance class: a wallet funded by an exchange + // resolves in one hop, and one funded through a chain of fresh + // intermediaries exhausts the budget. So the unresolved members are + // plausibly drawn from different classes than the resolved ones, and + // a difference between the two subsets can be manufactured entirely + // by that selection. + let under = |labels: &[String], unresolved: u64| -> bool { + let total = labels.len() as u64 + unresolved; + total > 0 && (labels.len() as u64) * 2 < total + }; + let a_under = under(&a, a_unresolved); + let b_under = under(&b, b_unresolved); + if a_under || b_under { + let who = match (a_under, b_under) { + (true, true) => format!("{label} and {against_label} both resolve"), + (true, false) => format!("{label} resolves"), + _ => format!("{against_label} resolves"), + }; + println!( + "REFUSING to rank these populations: {who} fewer than half its members.\n\n\ + What is left after the unresolved are dropped is each population's\n\ + *traceable* subset, and traceability is not independent of provenance\n\ + class — a wallet funded straight from an exchange resolves in one hop,\n\ + one funded through fresh intermediaries exhausts the budget. A difference\n\ + between two such subsets can be produced entirely by that selection, and\n\ + nothing in the numbers above would show it.\n\n\ + The figures are printed for completeness, not as a comparison. Closing\n\ + this needs resolution above half on both sides, which is a bigger\n\ + traversal budget rather than a different metric." + ); + return Ok(()); + } + if d.excludes_zero() { let (more, less) = if d.point > 0.0 { (&label, &against_label) diff --git a/crates/mirror-provenance/src/bootstrap.rs b/crates/mirror-provenance/src/bootstrap.rs index 105a8e64..f0705961 100644 --- a/crates/mirror-provenance/src/bootstrap.rs +++ b/crates/mirror-provenance/src/bootstrap.rs @@ -76,13 +76,25 @@ impl SplitMix64 { } } -/// A percentile interval, and the point estimate it surrounds. +/// The spread of the resampled estimator, and the estimate it was resampled +/// from. +/// +/// **This is not a confidence interval around `point`, and it is not guaranteed +/// to contain it.** It is the 2.5th-to-97.5th percentile range of `ρ̂` computed +/// over bootstrap replicates. For a statistic that is biased under resampling — +/// and `ρ` badly is, see [`Interval::resampling_bias`] — the whole range can sit +/// to one side of the original estimate. Reading it as "ρ is somewhere in here" +/// is wrong; it says "an estimate computed this way, from a sample like this one, +/// lands in here". #[derive(Debug, Clone, PartialEq)] pub struct Interval { /// The estimate computed from the sample as drawn. pub point: f64, pub lo: f64, pub hi: f64, + /// Mean of the bootstrap replicates. Its distance from `point` is the + /// resampling bias. + pub mean: f64, pub replicates: usize, } @@ -98,6 +110,30 @@ impl Interval { pub fn width(&self) -> f64 { self.hi - self.lo } + + /// How far resampling moves the estimator, `mean − point`. + /// + /// For `ρ` this is systematically **positive** under a heavy tail, and the + /// mechanism is worth understanding because it is not noise. Resampling `n` + /// members with replacement leaves roughly `1/e` of them unpicked, so classes + /// represented by a single member vanish from a replicate about 37% of the + /// time. Fewer classes means lower `H(C)` means higher `ρ = 2^{−H(C)}`. + /// + /// The size of this bias is therefore a **tail diagnostic**: a population + /// whose classes are mostly singletons shows a large one, and a population + /// with a few crowded classes shows almost none. + pub fn resampling_bias(&self) -> f64 { + self.mean - self.point + } + + /// Whether the original estimate falls inside the resampled range at all. + /// + /// False is not an error. It means the resampling bias exceeds the spread, + /// which is a real and reportable property of a heavy-tailed population — + /// not a sign that the interval was computed wrongly. + pub fn contains_point(&self) -> bool { + self.lo <= self.point && self.point <= self.hi + } } /// Default replicate count. Enough that the 2.5th and 97.5th percentiles are @@ -169,15 +205,17 @@ pub fn loss_factor_interval( } draws.sort_by(|a, b| a.partial_cmp(b).expect("no NaN in a loss factor")); + let mean = draws.iter().sum::() / draws.len() as f64; Some(Interval { point, lo: percentile(&draws, 0.025), hi: percentile(&draws, 0.975), + mean, replicates: draws.len(), }) } -/// A 95% percentile interval for `ρ(a) − ρ(b)`. +/// The resampled spread of `ρ(a) − ρ(b)`. /// /// Each population is resampled independently within a replicate, which is the /// right structure here: the two frames are drawn from different populations and @@ -215,10 +253,12 @@ pub fn difference_interval( } draws.sort_by(|a, b| a.partial_cmp(b).expect("no NaN in a loss factor")); + let mean = draws.iter().sum::() / draws.len() as f64; Some(Interval { point, lo: percentile(&draws, 0.025), hi: percentile(&draws, 0.975), + mean, replicates: draws.len(), }) } @@ -248,19 +288,64 @@ mod tests { assert_eq!(i.hi, 1.0); } - /// The interval must actually contain the estimate it surrounds. Trivial to - /// state and the first thing an off-by-one in the percentile index breaks. + /// With crowded classes, resampling barely moves the estimator and the + /// range does contain it. #[test] - fn the_interval_contains_the_point_estimate() { + fn a_crowded_population_resamples_around_its_estimate() { let l = labels(&[("a", 20), ("b", 10), ("c", 5), ("d", 5)]); let i = loss_factor_interval(&l, 2_000, DEFAULT_SEED).unwrap(); assert!( - i.lo <= i.point && i.point <= i.hi, + i.contains_point(), "point {} outside [{}, {}]", i.point, i.lo, i.hi ); + assert!( + i.resampling_bias().abs() < 0.05, + "crowded classes should barely shift: bias {}", + i.resampling_bias() + ); + } + + /// The case that caught a false assumption of ours, kept as a test so it + /// cannot be re-assumed. + /// + /// An earlier version asserted that the range always contains the point + /// estimate. It does not, and the reason is not a bug: under a heavy tail, + /// resampling drops singleton classes about 37% of the time, which lowers + /// `H(C)` and therefore raises `ρ` in nearly every replicate. The whole + /// range then sits *above* the original estimate. + /// + /// This is why the type is documented as the spread of the resampled + /// estimator rather than as a confidence interval, and why the bias is + /// reported next to it instead of being hidden inside it. + #[test] + fn a_heavy_tail_shifts_the_whole_range_above_the_estimate() { + // Thirty members, almost all alone: the regime every real run is in. + let mut spec: Vec<(String, usize)> = vec![("crowd".to_string(), 6)]; + for i in 0..24 { + spec.push((format!("solo{i}"), 1)); + } + let l: Vec = spec + .iter() + .flat_map(|(n, c)| std::iter::repeat_n(n.clone(), *c)) + .collect(); + + let i = loss_factor_interval(&l, 4_000, DEFAULT_SEED).unwrap(); + assert!( + i.resampling_bias() > 0.0, + "a heavy tail must bias rho upward under resampling, got {}", + i.resampling_bias() + ); + assert!( + !i.contains_point(), + "expected the range to sit off the point under this tail: \ + point {} in [{}, {}]", + i.point, + i.lo, + i.hi + ); } /// `analyze` is a pure pass and its output is a published number. The same diff --git a/data/sample-marinade-run7.json b/data/sample-marinade-run7.json new file mode 100644 index 00000000..4471957a --- /dev/null +++ b/data/sample-marinade-run7.json @@ -0,0 +1,5302 @@ +{ + "manifest": { + "endpoint": "https://mainnet.helius-rpc.com", + "first_available_block": 0, + "archival_probe_ok": true, + "collected_at": 1785043027, + "config": { + "scope": "Sol", + "depth_max": 16, + "min_edge_lamports": 500000, + "sig_page_cap": 24, + "page_size": 1000, + "birth_scan_cap": 24 + }, + "thresholds_hub_signatures": 5000, + "thresholds_distributor_fanout": 20, + "thresholds_cluster_min_size": 3, + "rpc_calls": 1887, + "excluded_non_wallet": [ + "71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip", + "DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8", + "DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv", + "Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK", + "DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN", + "3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi", + "83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg", + "7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd" + ], + "ambiguous_attribution_rate": 0.021897810218978103 + }, + "chains": [ + { + "seed": "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "visited": [ + "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "PageCapHit" + }, + { + "seed": "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "visited": [ + "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V", + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu" + ], + "stop": "PageCapHit" + }, + { + "seed": "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "visited": [ + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8", + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY", + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU", + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN", + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "visited": [ + "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "visited": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD", + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "stop": "PageCapHit" + }, + { + "seed": "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "visited": [ + "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" + ], + "stop": "PageCapHit" + }, + { + "seed": "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "visited": [ + "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "visited": [ + "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh", + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6", + "RBNV3esFPQ4jAkzW9hzxtZj34vJ9Snzrm6arXLs1mYZ" + ], + "stop": "LocalTerminal" + }, + { + "seed": "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "visited": [ + "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "stop": "PageCapHit" + }, + { + "seed": "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "visited": [ + "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw" + ], + "stop": "PageCapHit" + }, + { + "seed": "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "visited": [ + "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi" + ], + "stop": "LocalTerminal" + }, + { + "seed": "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "visited": [ + "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD", + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "stop": "PageCapHit" + }, + { + "seed": "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "visited": [ + "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c", + "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "visited": [ + "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw" + ], + "stop": "LocalTerminal" + }, + { + "seed": "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "visited": [ + "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ", + "visited": [ + "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ" + ], + "stop": "PageCapHit" + }, + { + "seed": "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "visited": [ + "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "visited": [ + "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4" + ], + "stop": "PageCapHit" + }, + { + "seed": "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "visited": [ + "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE", + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH", + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "stop": "PageCapHit" + }, + { + "seed": "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "visited": [ + "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm" + ], + "stop": "PageCapHit" + }, + { + "seed": "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "visited": [ + "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ", + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ", + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi", + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "visited": [ + "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i", + "visited": [ + "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i" + ], + "stop": "PageCapHit" + }, + { + "seed": "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq", + "visited": [ + "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq" + ], + "stop": "PageCapHit" + }, + { + "seed": "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "visited": [ + "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW", + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS" + ], + "stop": "PageCapHit" + }, + { + "seed": "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "visited": [ + "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK" + ], + "stop": "PageCapHit" + }, + { + "seed": "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "visited": [ + "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "visited": [ + "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn", + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq", + "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w" + ], + "stop": "PageCapHit" + }, + { + "seed": "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "visited": [ + "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "visited": [ + "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg", + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP", + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E", + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "visited": [ + "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "visited": [ + "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1" + ], + "stop": "PageCapHit" + }, + { + "seed": "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "visited": [ + "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r", + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J", + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "visited": [ + "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw" + ], + "stop": "PageCapHit" + }, + { + "seed": "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "visited": [ + "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn" + ], + "stop": "PageCapHit" + }, + { + "seed": "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "visited": [ + "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk", + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "stop": "PageCapHit" + }, + { + "seed": "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "visited": [ + "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "HK2mrz7pr8x9e4J7Bh1rsd6YidBWNk72fR3LUGpdy3hu" + ], + "stop": "LocalTerminal" + }, + { + "seed": "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "visited": [ + "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "visited": [ + "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "stop": "PageCapHit" + }, + { + "seed": "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "visited": [ + "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL" + ], + "stop": "PageCapHit" + }, + { + "seed": "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "visited": [ + "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "visited": [ + "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6", + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7", + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "stop": "PageCapHit" + }, + { + "seed": "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "visited": [ + "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "visited": [ + "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "visited": [ + "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "visited": [ + "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP", + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "stop": "PageCapHit" + }, + { + "seed": "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "visited": [ + "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4", + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "stop": "PageCapHit" + }, + { + "seed": "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA", + "visited": [ + "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA" + ], + "stop": "PageCapHit" + }, + { + "seed": "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z", + "visited": [ + "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z" + ], + "stop": "PageCapHit" + }, + { + "seed": "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "visited": [ + "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV", + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "stop": "PageCapHit" + }, + { + "seed": "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G", + "visited": [ + "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G" + ], + "stop": "PageCapHit" + }, + { + "seed": "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "visited": [ + "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "visited": [ + "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" + ], + "stop": "PageCapHit" + }, + { + "seed": "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "visited": [ + "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "stop": "PageCapHit" + }, + { + "seed": "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "visited": [ + "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z", + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG" + ], + "stop": "PageCapHit" + }, + { + "seed": "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "visited": [ + "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "visited": [ + "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "visited": [ + "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "visited": [ + "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "visited": [ + "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "visited": [ + "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL", + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "visited": [ + "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg", + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP", + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E", + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "visited": [ + "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "visited": [ + "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "visited": [ + "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "visited": [ + "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU" + ], + "stop": "PageCapHit" + }, + { + "seed": "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "visited": [ + "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "stop": "PageCapHit" + }, + { + "seed": "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "visited": [ + "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "visited": [ + "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq", + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + ], + "stop": "PageCapHit" + }, + { + "seed": "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "visited": [ + "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1", + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8", + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY", + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU", + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN", + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "visited": [ + "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep", + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "visited": [ + "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "visited": [ + "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW" + ], + "stop": "PageCapHit" + }, + { + "seed": "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "visited": [ + "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "visited": [ + "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "stop": "PageCapHit" + }, + { + "seed": "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "visited": [ + "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN", + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu", + "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk" + ], + "stop": "PageCapHit" + }, + { + "seed": "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "visited": [ + "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" + ], + "stop": "PageCapHit" + }, + { + "seed": "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb", + "visited": [ + "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb" + ], + "stop": "PageCapHit" + }, + { + "seed": "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "visited": [ + "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "stop": "PageCapHit" + }, + { + "seed": "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "visited": [ + "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "PageCapHit" + }, + { + "seed": "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "visited": [ + "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS" + ], + "stop": "PageCapHit" + }, + { + "seed": "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "visited": [ + "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "visited": [ + "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "visited": [ + "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "visited": [ + "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "visited": [ + "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "visited": [ + "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ", + "visited": [ + "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ" + ], + "stop": "PageCapHit" + }, + { + "seed": "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "visited": [ + "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm", + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL", + "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh" + ], + "stop": "PageCapHit" + }, + { + "seed": "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "visited": [ + "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM", + "visited": [ + "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM" + ], + "stop": "BirthScanExhausted" + }, + { + "seed": "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "visited": [ + "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + } + ], + "facts": { + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm": { + "address": "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784761350, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi" + ], + "birth_scan_exhausted": false + }, + "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4": { + "address": "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 6457 + }, + "first_seen": 1756313602, + "birth_edge": { + "sink": "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "sources": [ + "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V" + ], + "value": 103993966, + "signature": "55s64RhPGBNPTVDLsRnnXVpDJ5yKvuc5bW51GxXPJXNE3tRBWsSYt7nNovHNkQmP3WQD1s1hHS6RCXaec1ERULEK", + "slot": 363139506, + "block_time": 1756410719, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL": { + "address": "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL", + "owner": null, + "executable": false, + "signatures": { + "Exact": 81 + }, + "first_seen": 1736184345, + "birth_edge": { + "sink": "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL", + "sources": [ + "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh" + ], + "value": 3132408210, + "signature": "3GQxAV1idjVjzeuVGWgU2o4csbnbWrQK9VGHy8doD8WTcefK8y2VdnLuThnpUaMocGRhG2ErT86zPm5ZVMyqBQdT", + "slot": 312286142, + "block_time": 1736184345, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh" + ], + "funded": [ + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm" + ], + "birth_scan_exhausted": false + }, + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL": { + "address": "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783798894, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck" + ], + "birth_scan_exhausted": false + }, + "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk": { + "address": "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk", + "owner": null, + "executable": false, + "signatures": { + "Exact": 6 + }, + "first_seen": 1783099753, + "birth_edge": { + "sink": "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk", + "sources": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "value": 2039280, + "signature": "Hhyx93TXt6SLEjwsgZs3xDHb1jMgZffgEhuwMLhYYFRNqnzVcmGbo5xPrz3CGRDXVXQd3HWNbLYk2Qs2bUsz7Uf", + "slot": 430565619, + "block_time": 1783099753, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [ + "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh" + ], + "birth_scan_exhausted": false + }, + "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh": { + "address": "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 80 + }, + "first_seen": 1752673276, + "birth_edge": { + "sink": "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "sources": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "value": 12063418500, + "signature": "cKPChkvMEjtfwWt9C9UT6huX3cCp543m4ywRXBuBsuYWNKrDuvTCaJqSFFUym2mvuXMG1xCqWhQqvgUDjinFVJb", + "slot": 353692459, + "block_time": 1752673276, + "ambiguous_attribution": false + }, + "fee_payers": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK": { + "address": "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7954 + }, + "first_seen": 1774268614, + "birth_edge": { + "sink": "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "sources": [ + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ" + ], + "value": 5000000000, + "signature": "3D9VPk2WSdFidXqT2yc7QmN4so5tcRG8oq1RMMVN4Y81op9PcSrNPPhukqLvU2fmRPAncjyFp7csZxbee2uBg8qw", + "slot": 408325661, + "block_time": 1774268614, + "ambiguous_attribution": false + }, + "fee_payers": [ + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o": { + "address": "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 72 + }, + "first_seen": 1767875809, + "birth_edge": { + "sink": "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "sources": [ + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW" + ], + "value": 50000000, + "signature": "jAurnGX1dLaqmmcS95zSb1N2XZHj65BdUNAtchot1MWmx3z6We5f8Jew1sGu9NrQFrWGR8QdkMCSWSUAfu1yahT", + "slot": 392132125, + "block_time": 1767875809, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7": { + "address": "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 33 + }, + "first_seen": 1778858822, + "birth_edge": { + "sink": "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "sources": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG" + ], + "value": 10000000, + "signature": "41PgkwaTASYRwAhAmSQ6VMERq4xCjh5YNSsjmzdbU5VukKn6iMn1nzoyswB2wsJJVehVwKLspNmg4RpCKLptEtxL", + "slot": 419929899, + "block_time": 1778858822, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764": { + "address": "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 74 + }, + "first_seen": 1768845271, + "birth_edge": { + "sink": "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "sources": [ + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb" + ], + "value": 4000000000, + "signature": "buG8ygWB6h5RLJmX5caSxTXK786wYRXgwfS9RFABnCivqy6yNePkzmn3Hsyr3XVhs28D1qWgbXbThbyZUzymN6p", + "slot": 394584868, + "block_time": 1768845271, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv": { + "address": "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 351 + }, + "first_seen": 1715877601, + "birth_edge": { + "sink": "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 400000000, + "signature": "3vMHgLqfkYX6fLwgJKkv34rnViBSYsD2mwRJx5b2gBnMHjHemR7aMm4e12V7pwYUfDNDTNSctZ132e3GAixT72F8", + "slot": 266156059, + "block_time": 1715877601, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ" + ], + "birth_scan_exhausted": false + }, + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S": { + "address": "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1655911154, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV", + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH" + ], + "birth_scan_exhausted": false + }, + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS": { + "address": "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783794615, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "birth_scan_exhausted": false + }, + "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2": { + "address": "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 34 + }, + "first_seen": 1760264183, + "birth_edge": { + "sink": "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 21538335, + "signature": "4uNtV2g1sTqRYn7Skm6uidpEgFEL9UUnJhUGECL3rjyahb6vVPFPKQi3ZzV69NuPPPWwwmg7MNQnWQ59XBtqgP7S", + "slot": 372861338, + "block_time": 1760265242, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4": { + "address": "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1710631332, + "birth_edge": { + "sink": "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4", + "sources": [ + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39" + ], + "value": 394037978, + "signature": "3tJT334qtxVLAYkbJ8nGePCKiT4k9nP2DtwPgjwLiQuMbCMcMv6awuiXC19xq9zR9DUM3TAenySiEdiyp2uMygwB", + "slot": 254612541, + "block_time": 1710631332, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39" + ], + "funded": [ + "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg" + ], + "birth_scan_exhausted": false + }, + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j": { + "address": "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "owner": null, + "executable": false, + "signatures": { + "Exact": 499 + }, + "first_seen": 1683797389, + "birth_edge": { + "sink": "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 240000000, + "signature": "2THWQfqsFFiAxK6YpuFSVPJLzqz7mU7jXzvZ79f3aFWPfGCNR2HAhoFGKbhobBrKd4XJhRGd1guerxbyMx3wAn9V", + "slot": 193309880, + "block_time": 1683797389, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [ + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN" + ], + "birth_scan_exhausted": false + }, + "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM": { + "address": "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 270 + }, + "first_seen": 1649945488, + "birth_edge": { + "sink": "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 11854550000, + "signature": "5LojLQtWF5mFYcGE1MiSjmeixsnYuPneTWNigfaeMGJ1Yysw88ct5X7eaJdhRTy482fAXSM97EpWV6YhYBeBHQ7n", + "slot": 129710493, + "block_time": 1649945488, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi": { + "address": "3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr": { + "address": "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1785 + }, + "first_seen": 1739523472, + "birth_edge": { + "sink": "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "sources": [ + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" + ], + "value": 1987201900, + "signature": "bzNe8Zg7oE1fXGipW89PL11Q3LbWTdjkKzFGFfSHFnM1LZr93MSbyf5JhPNZ7khZ6twbx9RF41WCFySKZEj14NX", + "slot": 320565860, + "block_time": 1739523472, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ": { + "address": "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 457 + }, + "first_seen": 1638890356, + "birth_edge": { + "sink": "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ", + "sources": [ + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi" + ], + "value": 10000000, + "signature": "3FcspUwHdAXBUQUSD7W2h6rTetqRqEnwBGaviwuYHjBRfb6nP7dpJiGLn1s5CDCQ98neMBr4D8tRAQwCxaQRxtad", + "slot": 110473576, + "block_time": 1638890406, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2J2YzRzZ7YLNva22NVNJBQC4bbRVFoJgJsbxxArUNy88", + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi" + ], + "funded": [ + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ" + ], + "birth_scan_exhausted": false + }, + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw": { + "address": "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1779167369, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr" + ], + "birth_scan_exhausted": false + }, + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J": { + "address": "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1714594823, + "birth_edge": { + "sink": "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J", + "sources": [ + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX" + ], + "value": 500000000, + "signature": "5fTqyzGH9a9g4B626zks91Ln4aRi5iKy5NRGEK9LPy4c1E4XZXnNJK5raAxDDMoADYZDzyEinoYnbnbbS9NNu6w5", + "slot": 263327744, + "block_time": 1714594823, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX" + ], + "funded": [ + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r" + ], + "birth_scan_exhausted": false + }, + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS": { + "address": "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784049508, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW" + ], + "birth_scan_exhausted": false + }, + "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9": { + "address": "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 254 + }, + "first_seen": 1723048172, + "birth_edge": { + "sink": "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 490000000, + "signature": "4G2fwKebyh9QsNPjGHfPsCiBnjg5DYU99FUojtp5ZEcTnh5wwMxnYK7F11jQmdAsFSKSpxg1MPg67muc2FZroZrg", + "slot": 282148476, + "block_time": 1723048651, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF": { + "address": "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2386 + }, + "first_seen": 1770200497, + "birth_edge": { + "sink": "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "sources": [ + "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU" + ], + "value": 190000000, + "signature": "51n18JMEXfFtcFurC9Eyqe7ZVgmj4qcwLmrfsF6qDdodqteHt7D6vqyidRDdWZ1syv24c84Ri7jGCs6C8nHKokJR", + "slot": 397988088, + "block_time": 1770202555, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR": { + "address": "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 25 + }, + "first_seen": 1782895333, + "birth_edge": { + "sink": "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "sources": [ + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ" + ], + "value": 734553600, + "signature": "36vjnxndxfYcSHBf37rrdBPqcwc2BBpECPGQzSr6RNhbizneiaCKc4xXpiMNNGtk6eqnexRme4xfy3GDLfkPNyRk", + "slot": 430058672, + "block_time": 1782895333, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH": { + "address": "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11742 + }, + "first_seen": 1624516734, + "birth_edge": { + "sink": "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 100000000, + "signature": "fbtWCRyRCWNtSWCCZV2HJXPt9trL74715bnz1R13hWcBnNzSeJDm9Amufv5VFLQUUUXtVLM5nPCuWkteuBx83eW", + "slot": 84190784, + "block_time": 1624516734, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [ + "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf" + ], + "birth_scan_exhausted": false + }, + "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL": { + "address": "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18 + }, + "first_seen": 1758306486, + "birth_edge": { + "sink": "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "sources": [ + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep" + ], + "value": 2100000000, + "signature": "57LKR9sEPC21YHt2pbje7Ur5ACGLqqMUSF5wHYdquAVozDKs3K516nGr5sa47qJdKZiVvQnxTms11orYiYb7uvCP", + "slot": 367913728, + "block_time": 1758306486, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL": { + "address": "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 753 + }, + "first_seen": 1629221283, + "birth_edge": { + "sink": "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 5355472000, + "signature": "2W3H6wz4dVoB44Lnjkh5gnCUF4ZbgxqcmTruSYMG6vgfrP6Su4TeAcdfHTicxnRLqfasnbgZLjitokZRveP23Sp2", + "slot": 92116993, + "block_time": 1629221283, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [ + "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K" + ], + "birth_scan_exhausted": false + }, + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G": { + "address": "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5722 + }, + "first_seen": 1703416057, + "birth_edge": { + "sink": "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 970000000, + "signature": "3EzUczntHsFF95zi2Kt4Sq9Hh8tB932DRH2TfP3RoLXniCmJ9yBBhwdXqQHXv1w9bDHDWoxQQ6qSfeLaxhXqpf5j", + "slot": 237735212, + "block_time": 1703416057, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3" + ], + "birth_scan_exhausted": false + }, + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw": { + "address": "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784776619, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4" + ], + "birth_scan_exhausted": false + }, + "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7": { + "address": "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5237 + }, + "first_seen": 1757495135, + "birth_edge": { + "sink": "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "sources": [ + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm" + ], + "value": 2000000000, + "signature": "2j3tdTF7b9SQxw7tCmbtXBy8tWcoq9oMXAFBbRKVHkvHhu2VF3c9wDgAY3zVNcfHAojEvvkhQLr6oosvPosD8EjV", + "slot": 366585560, + "block_time": 1757781857, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3": { + "address": "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 119 + }, + "first_seen": 1756133000, + "birth_edge": { + "sink": "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "sources": [ + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G" + ], + "value": 59854218, + "signature": "39DqyYqU7RqGLLLSr8kU3mKL4xQnMVMbFjH5KN4XMoJvUDxBdYKGiwWPBGZWkASf2cYweXxb3wQ6utie6EMAANpu", + "slot": 364966880, + "block_time": 1757134451, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh": { + "address": "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20761 + }, + "first_seen": 1663879176, + "birth_edge": { + "sink": "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 490000000, + "signature": "5kDrBVEWkieCWXHps4mJ9h437RmD7D9Nmyo2uo2UwQsDNUWm6KiAB8T3DNQ8svrsbx11hqdXKcUBNQAY1gACJNwT", + "slot": 151875320, + "block_time": 1663879176, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh": { + "address": "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20 + }, + "first_seen": 1783099754, + "birth_edge": { + "sink": "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "sources": [ + "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk" + ], + "value": 2238177460, + "signature": "5a7NoCBgFaHpUJqMxVp6A8vBTLQnJ3uWB59UCDYZsTm3SyXmhYfrPkXSrPDJ9jca9LCtWM5eDLK4jUMoybZ4ApV4", + "slot": 430565641, + "block_time": 1783099762, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V": { + "address": "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1756410716, + "birth_edge": { + "sink": "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V", + "sources": [ + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu" + ], + "value": 2039280, + "signature": "qTMcW7d2WNayJ6STKme6zWHcpCvnZm5EKYRc5mQZJqqBMKB8T3pTKBnqrKQczdNSQfz6LZH1ETAJP7Yf9eiR8W7", + "slot": 363139499, + "block_time": 1756410716, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu" + ], + "funded": [ + "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4" + ], + "birth_scan_exhausted": false + }, + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7": { + "address": "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 15 + }, + "first_seen": 1734187345, + "birth_edge": { + "sink": "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7", + "sources": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "value": 139459250, + "signature": "3PEFFsvAZgWyxKcjNoWzSXQJ4XtMGZbsm4BDiXGtRVT2huaSmPbopJf6Psmsg5HJRxeLCo2ewLcvhQC6JkDNTDYj", + "slot": 307446440, + "block_time": 1734188936, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "funded": [ + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6" + ], + "birth_scan_exhausted": false + }, + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV": { + "address": "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV", + "owner": null, + "executable": false, + "signatures": { + "Exact": 55 + }, + "first_seen": 1773235824, + "birth_edge": { + "sink": "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV", + "sources": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "value": 532871511, + "signature": "nxEPT4eY8McAoUhK86oAYzDiTBXmjJgjmbGSdFaKbNZSNiSi9KoJAAP8hAKvQRZGEDExv1r77BapRvx1joJRXHf", + "slot": 405705084, + "block_time": 1773236286, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "funded": [ + "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ" + ], + "birth_scan_exhausted": false + }, + "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB": { + "address": "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 52 + }, + "first_seen": 1770266528, + "birth_edge": { + "sink": "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1204809220, + "signature": "373hVK6YWozQ1rjpFwQ9EYjzYKW7AJ3YEpfyAPGiDtdeMmRphi1bKSpnPn2vu8t8t7J7fKAug14jQu6LdRNUR4Jo", + "slot": 398150397, + "block_time": 1770266528, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ": { + "address": "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1778800198, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1": { + "address": "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784580609, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb" + ], + "birth_scan_exhausted": false + }, + "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC": { + "address": "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 824 + }, + "first_seen": 1757568229, + "birth_edge": { + "sink": "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "sources": [ + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN" + ], + "value": 800249721, + "signature": "hq8JsmuHcip8MBCGgPSwd7occB8hVnKNphP8Xm4Yk7Uuj9Vtkz5DCQEYMnh5W9bn74jGumD3LC6essjQdzqyWzp", + "slot": 366050029, + "block_time": 1757569412, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD": { + "address": "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1742045493, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr" + ], + "birth_scan_exhausted": false + }, + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt": { + "address": "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "owner": null, + "executable": false, + "signatures": { + "Exact": 124 + }, + "first_seen": 1773144352, + "birth_edge": { + "sink": "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 99000000, + "signature": "NbnA7s6aJWNM2nfCACoXNDeVGZwALbRcFCkM33u7mQyMKJ3QpSHhA1pVPVX3EugPo74gm1ycaL1CBJ4UqcN3dXi", + "slot": 405469329, + "block_time": 1773144352, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3" + ], + "birth_scan_exhausted": false + }, + "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z": { + "address": "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 96 + }, + "first_seen": 1628597237, + "birth_edge": { + "sink": "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 4990000000, + "signature": "2kUPLWWUJut6M2AfRAZAiGLkuWLJN5D4JXMZ1VKr7cj7uWWmmcY43cGMHS7UMF8Uw32rrFUdCMNh6AMyJKR7woy9", + "slot": 91034738, + "block_time": 1628597237, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ": { + "address": "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1782843699, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR" + ], + "birth_scan_exhausted": false + }, + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9": { + "address": "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784935407, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX", + "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G", + "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4" + ], + "birth_scan_exhausted": false + }, + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF": { + "address": "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784909391, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879" + ], + "birth_scan_exhausted": false + }, + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu": { + "address": "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3947 + }, + "first_seen": 1631763613, + "birth_edge": { + "sink": "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu", + "sources": [ + "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk" + ], + "value": 100000000, + "signature": "3CmYn4pR8Eaoa5ocbeLxZ9VHFTtQziQMVNpM2Pjh2V1HiJg3VCnkVskGWtaMVgf4oDRCiEW3RC8r9NbTgjY2aXQ5", + "slot": 96711846, + "block_time": 1631763613, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk" + ], + "funded": [ + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN" + ], + "birth_scan_exhausted": false + }, + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39": { + "address": "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1710624873, + "birth_edge": { + "sink": "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "sources": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "value": 394042978, + "signature": "5reS92mjj8Avu1Hm8NibK41HwVLTaetYhYEb1eGpT3AYkssVwhBEq479YqaVAw7G4tPhrnwvjUai3ruhQLpUW72T", + "slot": 254597447, + "block_time": 1710624873, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "funded": [ + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4" + ], + "birth_scan_exhausted": false + }, + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS": { + "address": "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 194 + }, + "first_seen": 1739874778, + "birth_edge": { + "sink": "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 1000000000, + "signature": "rB8uvcS1mATGDoAm47msudGAkdiXhSJBKGnk51uiWVkyzj45DKjiJUguciDjwujSbLGaxfhYYgC6bBs1bFMwbry", + "slot": 321451943, + "block_time": 1739874778, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [ + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL" + ], + "birth_scan_exhausted": false + }, + "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL": { + "address": "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 66 + }, + "first_seen": 1730986163, + "birth_edge": { + "sink": "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "sources": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "value": 240234545, + "signature": "4JGR17CZKiUrUKXVpnbhu4LzPvRc1vMRChQVcqLpjuWaPswEvUS3b8Cjf5tyNyuptuA5GGBrTt2cCgdv1QsYuwiQ", + "slot": 300009349, + "block_time": 1730986163, + "ambiguous_attribution": false + }, + "fee_payers": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk": { + "address": "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1667892868, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu" + ], + "birth_scan_exhausted": false + }, + "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv": { + "address": "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7163 + }, + "first_seen": 1776453682, + "birth_edge": { + "sink": "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "sources": [ + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1" + ], + "value": 11193190, + "signature": "2EFWC7p4UmzuhS8ewbcBi4bJGXe3Nd8tMbRpnLY4oCDG3MTJ1nxhsVLAhzcCHhEvacNdeoyepNE54ow4jY3WnZS5", + "slot": 413882415, + "block_time": 1776453892, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "CRUSHtWNSqWK4nu8zZvXpSKxKveas1LSuG7MycL4T976", + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9": { + "address": "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 41 + }, + "first_seen": 1771265328, + "birth_edge": { + "sink": "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1465532000, + "signature": "48jcsENEjMVCHrSfUqXLDZDt8nqQfHqDR1Fhx4VH3wGT5Epbks18Mfcs5WJ3yhzdnuWVupAUKPu6jgFTArDxM7iT", + "slot": 400700540, + "block_time": 1771265328, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV": { + "address": "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 22 + }, + "first_seen": 1781373138, + "birth_edge": { + "sink": "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "sources": [ + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq" + ], + "value": 19650094, + "signature": "5JwERz8A9ssUFHSgXzszdjoKkSs8WVRrjdmKqSRXeD7GcBCyDXqzgMjHQdtqUF9MgCA9R7cK4V7Vm9thS5ib7Fkg", + "slot": 426247804, + "block_time": 1781373138, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG": { + "address": "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1780571139, + "birth_edge": { + "sink": "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "sources": [ + "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW" + ], + "value": 146749060, + "signature": "GhK4Hst1KZyFkxo6viVKoxPJKD3LvVUz66T2joYoM5wacEkn1gbvDtFdNiLB1qXiCMsWiTdGay3xAV3sMHb4o9e", + "slot": 424231157, + "block_time": 1780571140, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU": { + "address": "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU", + "owner": null, + "executable": false, + "signatures": { + "Exact": 369 + }, + "first_seen": 1709827691, + "birth_edge": { + "sink": "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU", + "sources": [ + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN" + ], + "value": 50000000, + "signature": "51rFfVkiSmUzeh1QRNcm99Ea8MUGTMAff1SaRs2dGBigE9VBBo2kkNMsY1o85nxMF4jWHeSVQk2ctJZddtPcZQmj", + "slot": 252697500, + "block_time": 1709827691, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN" + ], + "funded": [ + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY" + ], + "birth_scan_exhausted": false + }, + "71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip": { + "address": "71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm": { + "address": "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784211013, + "birth_edge": { + "sink": "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "sources": [ + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE" + ], + "value": 3895835199, + "signature": "4pntPEhmoeiCTqii4XrvYMiAG2VVja2htxSyFFuda7S8MZfTLjQCkV9XmNyCSqekAzURhwHxAxSEMMApwkM6Zayj", + "slot": 433287475, + "block_time": 1784211013, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T": { + "address": "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 26 + }, + "first_seen": 1783100066, + "birth_edge": { + "sink": "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "sources": [ + "HK2mrz7pr8x9e4J7Bh1rsd6YidBWNk72fR3LUGpdy3hu" + ], + "value": 2247350044, + "signature": "2BdukFLQsAoYPAD8iQpWVwybrX2rZrhp46T5t1yco6c93fAAsY3wyyZdiRHmrBmDMKP1yaVjp4hmjqwa8A6bAoPr", + "slot": 430566389, + "block_time": 1783100066, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ": { + "address": "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2483 + }, + "first_seen": 1642454259, + "birth_edge": { + "sink": "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ", + "sources": [ + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ" + ], + "value": 100000000, + "signature": "4XUZRtA4Q1gAaiSWnDA7XY9RboU57S4bSkhfFscKU8Ywa8NioAKWpCu6Z9ZXRQ18qp2pQAFytHgibJ1So1dsiJ3f", + "slot": 121052633, + "block_time": 1644990871, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ" + ], + "funded": [ + "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK" + ], + "birth_scan_exhausted": false + }, + "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No": { + "address": "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4949 + }, + "first_seen": 1756672617, + "birth_edge": { + "sink": "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "sources": [ + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh" + ], + "value": 100000000, + "signature": "JTTb13ZHYiJd6AALvqyCnXhSZUFAXLFdKuQmPrXzPkVkYvzL16iDe8zYfBHmTtiLJkCay3pN8DjMdasCr2TqbWf", + "slot": 363900815, + "block_time": 1756711448, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "Hc2vsrdknaVuSypGNyMDuzR3FN3yWVcSuFALT6j2Cq71", + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW": { + "address": "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1769937907, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o" + ], + "birth_scan_exhausted": false + }, + "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg": { + "address": "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18410 + }, + "first_seen": 1710632250, + "birth_edge": { + "sink": "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "sources": [ + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4" + ], + "value": 394012978, + "signature": "3bvxtDsw7CBv3imPLoMrdyhA8cjSRRP3XCwzuJfyzx5AWZ9B8MjPdU3Gju47KgKxqGcd3gLP1ZhLVnHdhaZwQqYE", + "slot": 254614672, + "block_time": 1710632250, + "ambiguous_attribution": false + }, + "fee_payers": [ + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd": { + "address": "7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt": { + "address": "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1777777000, + "birth_edge": { + "sink": "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 9000000, + "signature": "48JSB2pqTRMXWg2S6ajewFiuyXADPk9q8QJYD1NLxSYwoBZzSFWKcPEKzQwN2K4dHJ6QYS8ppfo8Acra1LDZkffb", + "slot": 417226530, + "block_time": 1777777000, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg": { + "address": "83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4": { + "address": "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 650 + }, + "first_seen": 1764161875, + "birth_edge": { + "sink": "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 100000000, + "signature": "5Djf6M25i7Pqn3PvuibF3UkHSDJAmj3VEiHCuV7wDCth91DGunedSr5EaJ6KwDYQHyqLE6mGTbHM1K7hCg4ejans", + "slot": 382647096, + "block_time": 1764162226, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq": { + "address": "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 92 + }, + "first_seen": 1628599869, + "birth_edge": { + "sink": "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq", + "sources": [ + "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w" + ], + "value": 9880000000, + "signature": "3AVBmBvJmrKM8wMGddX2wk8Z2Jebsk4Tg17Gk1bM1YzMwaVqhqBeLLiU4uhEUeeXGa43AbPVKFCZeYDK3vDWPV5h", + "slot": 91039469, + "block_time": 1628599869, + "ambiguous_attribution": false + }, + "fee_payers": [ + "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w" + ], + "funded": [ + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn" + ], + "birth_scan_exhausted": false + }, + "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65": { + "address": "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1243 + }, + "first_seen": 1760985667, + "birth_edge": { + "sink": "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "sources": [ + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6" + ], + "value": 110000000, + "signature": "3eeFfzc6icmbz7XsoNRMKf1ZtnXKBMUWoXSjY5oarm3GXGzBMJ3QuxuFfMNVHuPtzp8EDsUMh7psK9B85v4QsZ1h", + "slot": 374677619, + "block_time": 1760987283, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6", + "Gem2VAypSg7Ai7vjDKPTtqFahpoQWkfgVkyzx3rPoTka" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z": { + "address": "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 510 + }, + "first_seen": 1736141750, + "birth_edge": { + "sink": "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "sources": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "value": 10450531000, + "signature": "5NTDt6Rswtrvt42AT1trjiRspELnF9t98z5H2TyQ4D1L7zB84w3GQjF6borcJLChNK89mRwQUTsqpowEU6MMp4yQ", + "slot": 312181544, + "block_time": 1736141750, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn": { + "address": "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 928 + }, + "first_seen": 1633089324, + "birth_edge": { + "sink": "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn", + "sources": [ + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq" + ], + "value": 1000000000, + "signature": "qjyV7pemydByaUSd3gd8tGw1uo1JZjM1eg2uKzR8jEs5U6zL1pJRN88EnT6cKsECcGegdcAV5pPbfxkhPJb2B1w", + "slot": 99242855, + "block_time": 1633089324, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq" + ], + "funded": [ + "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i" + ], + "birth_scan_exhausted": false + }, + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS": { + "address": "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1773493800, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs" + ], + "birth_scan_exhausted": false + }, + "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K": { + "address": "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4311 + }, + "first_seen": 1729158047, + "birth_edge": { + "sink": "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "sources": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "value": 200000000, + "signature": "29ZCJmU48wWT7mGpV6u7YsVzcc15Fy6CDPRWRLgjqNYfcgMUU75z79L3ruGa5wXW68AfU9m4EQzr2WtsZ1gmHRgs", + "slot": 299468871, + "block_time": 1730727939, + "ambiguous_attribution": false + }, + "fee_payers": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck": { + "address": "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 37 + }, + "first_seen": 1781689177, + "birth_edge": { + "sink": "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "sources": [ + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL" + ], + "value": 2000000000, + "signature": "2kW6Rf9kPJAZnCSgHodJwFNifHr5L1UN3Cj8afcoWHsxHKWFiDEfzFv7v8DsvNL5HFQeihiYy4GWieL826rQnH9Y", + "slot": 427044455, + "block_time": 1781689177, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr": { + "address": "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1782983366, + "birth_edge": { + "sink": "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "sources": [ + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw" + ], + "value": 65793663482, + "signature": "4uU5EhqsUfPVzpZ1jHMrg9T2TgfcMWE6s81PwMypZnVXQzjKNiB4ibWb9E2MPHa5WFC5mkZhf2DN4PRtX5zaCnB3", + "slot": 430277120, + "block_time": 1782983366, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh": { + "address": "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1028 + }, + "first_seen": 1736684311, + "birth_edge": { + "sink": "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh", + "sources": [ + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6" + ], + "value": 30000000, + "signature": "3tBtrKN5a4FNtR2Tti1gYDwxnWoZ9KhNXu1kP4ArQG4q5wDFvU2C2Z3vxS8hgZu2eLkmcT8XR8MfzLHtNfjimhfc", + "slot": 313511470, + "block_time": 1736684311, + "ambiguous_attribution": false + }, + "fee_payers": [ + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6" + ], + "funded": [ + "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No" + ], + "birth_scan_exhausted": false + }, + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E": { + "address": "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 346 + }, + "first_seen": 1619103636, + "birth_edge": { + "sink": "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E", + "sources": [ + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX" + ], + "value": 3999946680, + "signature": "5Xn9C17aBL4Qnk7ZwPJrJeT25CNbP7u7FVGh3qEz3eXKCuqeyM5UNz5KBiW42J3vboohK1ucyZAfeukZybth93Rp", + "slot": 74825696, + "block_time": 1619103636, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX" + ], + "funded": [ + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP" + ], + "birth_scan_exhausted": false + }, + "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb": { + "address": "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 87 + }, + "first_seen": 1761613462, + "birth_edge": { + "sink": "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 5504330, + "signature": "4X7t1Ls8Zpb3bqFZchjukeaG9Tf1MdMwUS62Ua2Nx4ALiEYLR4aSCr2Az9hTVF17HBnJsPUa4Q2amcmFnXWECY9m", + "slot": 376252987, + "block_time": 1761613462, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879": { + "address": "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 59 + }, + "first_seen": 1760849092, + "birth_edge": { + "sink": "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "sources": [ + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "value": 93302400, + "signature": "8d5pfjnoehDwiQkD8Uzm2oPh1t4kFwufYmpALykZYwM5KuTQ4dnfNcNHsWS55QDSLYD8mB3YK18epo2sfgEtnY7", + "slot": 376261031, + "block_time": 1761616657, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV": { + "address": "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1764737043, + "birth_edge": { + "sink": "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 14000853800, + "signature": "4HrqBiVtSmNYMccmWJTEBTXqQe3eztKrd3bpj9AibBehzd7QGCqiY69pbSafREg2UaAHGcv5zt1TU57vbjDFVJdx", + "slot": 416969897, + "block_time": 1777675621, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "7dq4McdQAd6ZUMajZKHNiacNuWcyJsBj1eMeZPPckyz1", + "75Cxo2H9iFZ71LVWADz1qwRV2MJkjzmUdo2b6UjXp1KN", + "6cRBcJy8YatfNYLHD5kyNtCX9ypENwXerB36MKUNUK4e" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR": { + "address": "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784454505, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh" + ], + "birth_scan_exhausted": false + }, + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2": { + "address": "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783242961, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7", + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z" + ], + "birth_scan_exhausted": false + }, + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE": { + "address": "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 27 + }, + "first_seen": 1726629672, + "birth_edge": { + "sink": "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE", + "sources": [ + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH" + ], + "value": 1000000000, + "signature": "4DvouS9aXw7ir6N3ohxgKFw3UugY7jqK3aextkEtbox1i1wue5Xh4W6NHbZE9bWvZLMb1P28LoMDY4QhBnRXcehr", + "slot": 290465228, + "block_time": 1726629672, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH" + ], + "funded": [ + "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm" + ], + "birth_scan_exhausted": false + }, + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP": { + "address": "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 8186 + }, + "first_seen": 1711915637, + "birth_edge": { + "sink": "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP", + "sources": [ + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V" + ], + "value": 200000000, + "signature": "2sbPicuC4gBQYMAM312LbgX1eDfY9nWY1Aeomg7RBbYVq4ZBuxkFJ1Cpc1icLyVxYUvzPmUyBdn5tCjBbBgRco49", + "slot": 257602531, + "block_time": 1711915637, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V" + ], + "funded": [ + "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n" + ], + "birth_scan_exhausted": false + }, + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri": { + "address": "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 10 + }, + "first_seen": 1742326938, + "birth_edge": { + "sink": "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 1003215950, + "signature": "3tm1fffHfNDLhNDnTLeRVe5scKp75BZtrAEcbiDoujfP9be6qpAEwP1heKegTaC1jYcGo8641hi94FUuaj6sZrJF", + "slot": 327633737, + "block_time": 1742326938, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [ + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep" + ], + "birth_scan_exhausted": false + }, + "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU": { + "address": "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1769207730, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF" + ], + "birth_scan_exhausted": false + }, + "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa": { + "address": "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 81 + }, + "first_seen": 1712036070, + "birth_edge": { + "sink": "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 270768410, + "signature": "4MjcwLDx2bPhGuHdRFGKiE3khiQoSd5DWZEw7RNbuHTubuJ6MvHtZyG4TA7TTAHc9CeK6yHS68ErF5gZdeKnDC2R", + "slot": 257873921, + "block_time": 1712036070, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX": { + "address": "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3735 + }, + "first_seen": 1760474300, + "birth_edge": { + "sink": "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "sources": [ + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD" + ], + "value": 100000000, + "signature": "aq7ps7k6RZcBWVMaHcqHLVemRNLYzFQukxseeeApwgmVCZ39LNCa2kh8WLCkkdM21AUUjVv9sYtYmi9Gz9BwkUB", + "slot": 373387624, + "block_time": 1760474300, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4": { + "address": "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 37 + }, + "first_seen": 1775710580, + "birth_edge": { + "sink": "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "sources": [ + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw" + ], + "value": 4639648909, + "signature": "3yQYxKyRPrcH6pk87zfhx67eL6Wzza7hDrE91MA3xHKBRcWv6fHLYeW2JfwTtG2sHw5u67qig7NJ2usnX26aqb6t", + "slot": 412000930, + "block_time": 1775710580, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8": { + "address": "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 179 + }, + "first_seen": 1739919851, + "birth_edge": { + "sink": "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8", + "sources": [ + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY" + ], + "value": 625744930, + "signature": "3RQ4iKJW9dUmd47Tr4JANamMYoeg33wQPZvLCPB2Xy5SGpB7BjfcwYoV98YCvvbZyjJ8hgrEB3ybUJpTQrBke1oX", + "slot": 321565671, + "block_time": 1739919851, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY" + ], + "funded": [ + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf" + ], + "birth_scan_exhausted": false + }, + "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf": { + "address": "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 450 + }, + "first_seen": 1713195781, + "birth_edge": { + "sink": "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "sources": [ + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ" + ], + "value": 10000000, + "signature": "4CEipGPf7m8m8EfHLDWVTy1TsiKyD1LsD6ZAaaNNgvwWJdvqGxRoo9cPcFbASE47phKqZFzaG9dWKaTCFx99bTSb", + "slot": 260290481, + "block_time": 1713195781, + "ambiguous_attribution": false + }, + "fee_payers": [ + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS": { + "address": "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784772742, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD", + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL" + ], + "birth_scan_exhausted": false + }, + "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh": { + "address": "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1633341120, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL" + ], + "birth_scan_exhausted": false + }, + "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc": { + "address": "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 64 + }, + "first_seen": 1778266848, + "birth_edge": { + "sink": "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 99000000, + "signature": "3UDS6u6ncvTgLndGKD9eaT6q5NWG6JvBniKFJmXeXWPcn9uPsEb5vLWyerKgsPfDvS76r5og2onyCsvXhDw86Kiz", + "slot": 418457793, + "block_time": 1778267806, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i": { + "address": "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 290 + }, + "first_seen": 1757844826, + "birth_edge": { + "sink": "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "sources": [ + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn" + ], + "value": 120000000, + "signature": "5aVXbSHPmaWfGTctRcFzMuXnqujTKZAEYTX9G6cByRFswAMPGesDy2vVKWp1e9sykcHkF2QbnhiBQQgbZMhPs7EL", + "slot": 366747475, + "block_time": 1757845725, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn": { + "address": "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784572951, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa" + ], + "birth_scan_exhausted": false + }, + "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM": { + "address": "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 490 + }, + "first_seen": 1757104043, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "2nh9AXgxVtbDucnR6Psv53JZaWvjphDLMb6HohuKmr5e", + "pay3JiXTbGTdjfoYo1caZ1oGdQgWVLQ3ws8LoqbxU8i" + ], + "funded": [], + "birth_scan_exhausted": true + }, + "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ": { + "address": "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 440 + }, + "first_seen": 1773406077, + "birth_edge": { + "sink": "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "sources": [ + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV" + ], + "value": 3164021672, + "signature": "cTRx38ZKrtf7ffFXLkr2E2zVgVeJBugExzbDBrxPH7AnZdU631jwEpEQwgsspZEV8QLGr39u9N9nU3vdSrK7RMi", + "slot": 406138581, + "block_time": 1773406090, + "ambiguous_attribution": false + }, + "fee_payers": [ + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij": { + "address": "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2605 + }, + "first_seen": 1753207964, + "birth_edge": { + "sink": "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "sources": [ + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD" + ], + "value": 140590159, + "signature": "2oaGcywpLPt8xEY7w84WSw4Z94DJiZgz1sTYzpCBbfT8sc5UYD99TiHCZoJqasjmLGzqUbffbHXg7LimmCk5pzu4", + "slot": 355044244, + "block_time": 1753207964, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe": { + "address": "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 320 + }, + "first_seen": 1720840348, + "birth_edge": { + "sink": "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 11000000000, + "signature": "1rHtsQspU78fmcbJZDfTh4hrN8xcRLjz2Ji2N7VV5KPBW29nATa6FSmt6WsnrFkmnNbz7QmuQiCDEgeRJYieX9E", + "slot": 277240691, + "block_time": 1720840348, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU": { + "address": "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784918904, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8" + ], + "birth_scan_exhausted": false + }, + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6": { + "address": "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784392728, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH" + ], + "birth_scan_exhausted": false + }, + "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb": { + "address": "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1742345740, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg": { + "address": "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2175 + }, + "first_seen": 1656406760, + "birth_edge": { + "sink": "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg", + "sources": [ + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP" + ], + "value": 9995000, + "signature": "uuctf7AvHunnmw7gS6Vn6WdB14iBXvbSCkNj7hacJUYDiFs41nFhzzoP2NqGHfV1HH1pFBEmiMsdRW8tXAmv8Qr", + "slot": 140737056, + "block_time": 1657295936, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP" + ], + "funded": [ + "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL" + ], + "birth_scan_exhausted": false + }, + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb": { + "address": "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "owner": null, + "executable": false, + "signatures": { + "Exact": 8 + }, + "first_seen": 1768750363, + "birth_edge": { + "sink": "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 30193550020, + "signature": "4sv5REU2obsMfmgbTmSqWgiGiCnQJibid6PgNptnSpqaDLjAWGBdfotB1yiG3tnDuJv7mqRN5NyXQAstjw1QfMdr", + "slot": 394345708, + "block_time": 1768750363, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [ + "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764" + ], + "birth_scan_exhausted": false + }, + "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm": { + "address": "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 143 + }, + "first_seen": 1767909246, + "birth_edge": { + "sink": "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "sources": [ + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4" + ], + "value": 1310000000, + "signature": "3kDnzFaFpXEegPizm9MKtpGGJ8BnETNFdYFJpd2rxKux7Z8EF58v5sMGsnqHfGAxoPTkiZCcSUUb9VpqWL7LD4og", + "slot": 392216036, + "block_time": 1767909246, + "ambiguous_attribution": false + }, + "fee_payers": [ + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ": { + "address": "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1763677438, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD": { + "address": "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1058 + }, + "first_seen": 1734856327, + "birth_edge": { + "sink": "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD", + "sources": [ + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "value": 491810375, + "signature": "2dMS6hZn46wpPzfXrzPMP6u4qrA8E21Pn5vhdeZGvEtMVxozhJUucnm8XfFiWA7VyrcvZCe8zVQYPdFnGx833BxP", + "slot": 310396534, + "block_time": 1735409017, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HgsxLyn8175xEwRffPRN3DeARE2EVcEeXENr12HpadL6", + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "funded": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG" + ], + "birth_scan_exhausted": false + }, + "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG": { + "address": "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 36 + }, + "first_seen": 1778449810, + "birth_edge": { + "sink": "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "sources": [ + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z" + ], + "value": 200000000, + "signature": "4XRjYBMjWrSKs8PTEPYrTgkfwVw8Er4AjCYaiHtVn9L5tRWTGu7HnxBTSKUc6gGYGoLri2jr3U9dJ3HnvDvnz1HE", + "slot": 418909903, + "block_time": 1778449810, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG": { + "address": "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1781067356, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z" + ], + "birth_scan_exhausted": false + }, + "Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK": { + "address": "Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt": { + "address": "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 17 + }, + "first_seen": 1783210007, + "birth_edge": { + "sink": "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 2290497690, + "signature": "3dZV999nzMy8zrpKZkKbhczT6DLA7J9YhLZZZKpFD6U6UoGg9oEUrySMR4VJNGeV6UTJ8rKbq6qLpL3AQ91UWu5S", + "slot": 430838822, + "block_time": 1783210007, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj": { + "address": "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 126 + }, + "first_seen": 1775679727, + "birth_edge": { + "sink": "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "sources": [ + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL" + ], + "value": 346000000000, + "signature": "2qWrYX2VaSpYFeYPpkqG22UArD9XFzXGYmLbcvSD9zNTGSyjB87ZKsAVfq16XzCazqppJMkRB9XG9rMUJBEf6Z29", + "slot": 411923073, + "block_time": 1775679846, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c": { + "address": "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 35 + }, + "first_seen": 1775705585, + "birth_edge": { + "sink": "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c", + "sources": [ + "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ", + "8ncU5YW1CQwvr4gs7buH57bW58e86TDau4STrCJBuz8z", + "FUYyfnVhhRc3PoAyQhWYVGL5oN7BF69R6oSKFjz7jtEh" + ], + "value": 58972050, + "signature": "4Vj6GXww18MxSkppTLZFUPxyjKQQMXgohde7sNGCwZGwgQAiyAr31p73V59467WC1g9iFxPeopMWHAdz3yPdqGsW", + "slot": 411994454, + "block_time": 1775708022, + "ambiguous_attribution": true + }, + "fee_payers": [ + "4PqrAP81NRSgbeBNG3scWVev8RAiy1B5SgRDDo3Ub6Eu", + "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ" + ], + "funded": [ + "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28" + ], + "birth_scan_exhausted": false + }, + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN": { + "address": "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1565 + }, + "first_seen": 1700506935, + "birth_edge": { + "sink": "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN", + "sources": [ + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j" + ], + "value": 1000000000, + "signature": "3ahr5wC3H6gNPGRX2fZbnSgnSwWo5A7rKcBcpr2MTvpk7LJbALNkrdYuFLunGG3myzFZYMC8ZzPJEtAgzXWbeqrm", + "slot": 231217678, + "block_time": 1700506935, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j" + ], + "funded": [ + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU" + ], + "birth_scan_exhausted": false + }, + "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3": { + "address": "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 87 + }, + "first_seen": 1775659926, + "birth_edge": { + "sink": "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "sources": [ + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt" + ], + "value": 57015551, + "signature": "4U4QtmZTpQ15CfqzPENGoU8QKmkR4M3MzAChG1ff7qZQVRFcizNEfrp1JqoaZaAYJSYnvZB9z1fh5hs3rD2Qxa22", + "slot": 411872556, + "block_time": 1775659926, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD": { + "address": "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 159 + }, + "first_seen": 1759568115, + "birth_edge": { + "sink": "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "sources": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "value": 8793000, + "signature": "21n3KFdeWHq3Xb5KkQL5N9gb29bMcU9zgkBmHFK2EnKqpMCpNfhTmMC14oLPRoFMzrcrrUUp34sb1a7ehfKFByE1", + "slot": 371102052, + "block_time": 1759568115, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "funded": [ + "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX" + ], + "birth_scan_exhausted": false + }, + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf": { + "address": "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784791291, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh" + ], + "birth_scan_exhausted": false + }, + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA": { + "address": "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784869182, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk" + ], + "birth_scan_exhausted": false + }, + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep": { + "address": "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 394 + }, + "first_seen": 1746944587, + "birth_edge": { + "sink": "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep", + "sources": [ + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri" + ], + "value": 518078106, + "signature": "4jjjqQ8p9W4WSG7QFKS8FiQszm6XA3gr7ChHQzeeN9nBxWX5DMSErsAunEVF3t1y2Znu2CP95CtjKB8GA4qf8Jz2", + "slot": 339255278, + "block_time": 1746946824, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri" + ], + "funded": [ + "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL" + ], + "birth_scan_exhausted": false + }, + "DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv": { + "address": "DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8": { + "address": "DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN": { + "address": "DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8": { + "address": "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 53 + }, + "first_seen": 1781684439, + "birth_edge": { + "sink": "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "sources": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "value": 38618000, + "signature": "5U5bvAmXWokcz4KHXizva51UQDG5PaZyLxQd7fECRdQ8VdX54ZjeW4bf6xBXAvqfLQWJ3394RDzjBbp3UTQX1JiK", + "slot": 427034554, + "block_time": 1781685267, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY": { + "address": "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 527 + }, + "first_seen": 1713772073, + "birth_edge": { + "sink": "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY", + "sources": [ + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU" + ], + "value": 10000000, + "signature": "3HHA4jtCTpR6BeP3G4CfXDsDgX57eVVUQrQZyeosgzrbkhLsjtai66LSgF75UmzWp86gyVWbzLe9PnkJhzzDNCew", + "slot": 261568924, + "block_time": 1713772073, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU" + ], + "funded": [ + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8" + ], + "birth_scan_exhausted": false + }, + "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z": { + "address": "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1760108855, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP": { + "address": "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5145 + }, + "first_seen": 1655204850, + "birth_edge": { + "sink": "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP", + "sources": [ + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E" + ], + "value": 100000000, + "signature": "5qCwaVyRDsEr4C3ZndJF99BkYmkRzrGycZXnShE9GujHLnD3CuCc3ETcUXSAop85SrPHBwdjmUdc51yV3GkCYfC3", + "slot": 137511084, + "block_time": 1655204887, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E" + ], + "funded": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "birth_scan_exhausted": false + }, + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX": { + "address": "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX", + "owner": null, + "executable": false, + "signatures": { + "Exact": 30 + }, + "first_seen": 1714060177, + "birth_edge": { + "sink": "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 987749310, + "signature": "4T9BG6pUpJCYHdubvDe8piyaNYdHW1i8L3Aa3rGokj11eZmiipZBSLJS8updgukiSsjwakJuXsznzjRfcW7hfrhe", + "slot": 262187645, + "block_time": 1714060177, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J" + ], + "birth_scan_exhausted": false + }, + "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK": { + "address": "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1190 + }, + "first_seen": 1747220417, + "birth_edge": { + "sink": "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "sources": [ + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm" + ], + "value": 3141592653, + "signature": "3mXJiiZA35B1Y6UUyqkynZBkYe8sM4WsjzRBgSfjrvqYRhSRfWSx25Xdr5ct4mFR2UpbTJQg1goGscDGfRa3NdE3", + "slot": 339946664, + "block_time": 1747220417, + "ambiguous_attribution": false + }, + "fee_payers": [ + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi": { + "address": "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 200 + }, + "first_seen": 1653138893, + "birth_edge": { + "sink": "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "sources": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "value": 606489437, + "signature": "62pVXVgr23rpDrywbMT9my8PUPDsRf53biB4QSm9oa67zvw14JmuaqbuC5obnSjSYS1acXMQQRgUXVYpX2s1RHaF", + "slot": 134659485, + "block_time": 1653138893, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa": { + "address": "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1783201670, + "birth_edge": { + "sink": "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "sources": [ + "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn" + ], + "value": 185933290, + "signature": "NX3DrMfLgUE4yHC8dgbQj2vHFKiCzTrPhfTDShToZdn8skzKWLJyzEEh6dbWxeMTBEkRhqWkXVRNzmZDvGKs3Yc", + "slot": 430818102, + "block_time": 1783201670, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3ddCq8LgnyYiJFfupiNAR8rQ9BshepSMzSwWbNLageTx" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq": { + "address": "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 17 + }, + "first_seen": 1784199361, + "birth_edge": { + "sink": "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "sources": [ + "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw" + ], + "value": 23682958, + "signature": "2ChU8gFx2ffsnkR6HwHJsD2Pd44iTWQ37rdfWgHwbVjMYb45X88VvqNSCaZu6vh2fgC62bXvkt6C8v9YzUrjNz26", + "slot": 433259174, + "block_time": 1784199361, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9": { + "address": "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20 + }, + "first_seen": 1782002060, + "birth_edge": { + "sink": "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 50000000, + "signature": "RtgJqd5sHDiondx4Z2yiq5essG1zXj5XwroXbZrtjkhy9NDPjutCezrPBr122sZqphKBa9YWJF3VPxiBUerRjBq", + "slot": 427833377, + "block_time": 1782002060, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U": { + "address": "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 10 + }, + "first_seen": 1781081436, + "birth_edge": { + "sink": "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 92849110, + "signature": "2d8HpMfLa5r1S3vdLKNiT69B7gPFjj4U3YHRc3skWQFqF3T2LUjTQzjPtWiHzQytw3VupQMQFiajPmjNYzN5rZfe", + "slot": 425512751, + "block_time": 1781081436, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH": { + "address": "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1703354625, + "birth_edge": { + "sink": "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH", + "sources": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "value": 1320200000, + "signature": "3cqmB1Yi2Z7ZVQjjA37NQ4d84ViimuKf1h83J23digAMRGoJMY8VmUbNkSxyqxRqsE3qBoBFKwCMXfxhVMEGNacP", + "slot": 237597989, + "block_time": 1703354625, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "funded": [ + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE" + ], + "birth_scan_exhausted": false + }, + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm": { + "address": "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 57 + }, + "first_seen": 1756226352, + "birth_edge": { + "sink": "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm", + "sources": [ + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL" + ], + "value": 19129985905, + "signature": "hwFDdbQ5JJzyNKNESkkX193G8qp1cXkGT1GJfmz6Aca26Kh6Qo54vbWPejmst4DCTFtUW7ifDYZ1XG2UFgru68q", + "slot": 362671528, + "block_time": 1756226352, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL" + ], + "funded": [ + "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7" + ], + "birth_scan_exhausted": false + }, + "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW": { + "address": "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1780571139, + "birth_edge": { + "sink": "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW", + "sources": [ + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS", + "ATRsNGv2nDw7hSMfkUTBoVUDsFDwN7po7KbecyiGWNB4" + ], + "value": 148788340, + "signature": "5qzt8zfBQNcj1k4sSPUrPrxcBYLMNDTg44S69HPqnkuwYNFXqhc6uJYS8ftreQQPkpNkFjk3nH8bFV5d5M7bWLXh", + "slot": 424231154, + "block_time": 1780571139, + "ambiguous_attribution": true + }, + "fee_payers": [ + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS" + ], + "funded": [ + "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG" + ], + "birth_scan_exhausted": false + }, + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi": { + "address": "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 924 + }, + "first_seen": 1624797610, + "birth_edge": { + "sink": "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi", + "sources": [ + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV" + ], + "value": 30170000000, + "signature": "VRM4EXex3RUYf4mnPB4E6jv9D7XmkmHdwbzeXTCTjmCqWpSfq7mKhgRibqwyzFXvu4FjpvYK5NeQrsQ6vbQ6e8A", + "slot": 84652957, + "block_time": 1624797610, + "ambiguous_attribution": false + }, + "fee_payers": [ + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV" + ], + "funded": [ + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ" + ], + "birth_scan_exhausted": false + }, + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu": { + "address": "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1774369160, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V" + ], + "birth_scan_exhausted": false + }, + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r": { + "address": "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 6850 + }, + "first_seen": 1714596610, + "birth_edge": { + "sink": "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r", + "sources": [ + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J" + ], + "value": 499994950, + "signature": "32pf2BhJSmb5gunpiH6aruXu1ErmFsqfsuSuXZCrWYfk1cQGrKa1SUC8A2QEeASEkHt14zDoAJs3vJwq1CFrMoNe", + "slot": 263331626, + "block_time": 1714596610, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J" + ], + "funded": [ + "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo" + ], + "birth_scan_exhausted": false + }, + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe": { + "address": "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1785035720, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2" + ], + "birth_scan_exhausted": false + }, + "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo": { + "address": "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13145 + }, + "first_seen": 1719672752, + "birth_edge": { + "sink": "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "sources": [ + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r" + ], + "value": 150000000, + "signature": "5eDgfMWctbmhkp9ZUddUGf5pcyvMr1ReaEK9nXEYipstcuD58uVbr7UVEzkEv2XoVf464y62Wj9f8mfbfkrg3duL", + "slot": 274705160, + "block_time": 1719673479, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r", + "3qaRfWYQitbWQe7CHVvUR4QwQpG7xcaGmtpfeA3LKrXR" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G": { + "address": "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784842995, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6": { + "address": "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1439 + }, + "first_seen": 1739722292, + "birth_edge": { + "sink": "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6", + "sources": [ + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7" + ], + "value": 4251664084, + "signature": "2PSni2jfVeX9YUufMwGVXWanasSbzHYk7imdYtwDjjSAqERG6T7s4Hx6DWStky2bqrYgpqoqR7xy6KjTbdQ5tC9M", + "slot": 321067692, + "block_time": 1739722292, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7" + ], + "funded": [ + "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65" + ], + "birth_scan_exhausted": false + }, + "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq": { + "address": "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1775881597, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf": { + "address": "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11491 + }, + "first_seen": 1770490600, + "birth_edge": { + "sink": "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "sources": [ + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8" + ], + "value": 11251120, + "signature": "5QwjQCxGUJfMWx2A5LAZoMeXvTKoT2yUU4GgXmo38bpidM3MWsqEouL2qi5Hbnh6ahYraSuUt3dNTAEW6dY9s47C", + "slot": 398740672, + "block_time": 1770498005, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8" + ], + "funded": [ + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1" + ], + "birth_scan_exhausted": false + }, + "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA": { + "address": "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 120 + }, + "first_seen": 1769087495, + "birth_edge": { + "sink": "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "sources": [ + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" + ], + "value": 430752000, + "signature": "2AFuX4tvKhsFguYnNfiW4QCHsYFeoARyYNbm9hMxqKHpgaX2f6d8qHfioWQsnyDCy5wFHzeUAXoSURUyGDGgmxzj", + "slot": 395193315, + "block_time": 1769087495, + "ambiguous_attribution": false + }, + "fee_payers": [ + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD": { + "address": "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1211 + }, + "first_seen": 1731582585, + "birth_edge": { + "sink": "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 146500000, + "signature": "3rHVM8ZmNB7DinaukQx8mKva26Y4YUNNXmxtPwwjZEW5Gpy72tqpWjfRBCW6jZR64WDC5QxLEFiMkAPKNK65MKTY", + "slot": 301468444, + "block_time": 1731640101, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [ + "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij" + ], + "birth_scan_exhausted": false + }, + "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p": { + "address": "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 282 + }, + "first_seen": 1707956423, + "birth_edge": { + "sink": "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 92000000, + "signature": "2Zo3SqZ1zdcF99BygckTotHufbXupGv4FLFyEdVxbSwKedaeJRsp2gb8Uwjgjf8PkwQgsibXyJvpBZwFErrJ6FDc", + "slot": 248216739, + "block_time": 1707956423, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ": { + "address": "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1770144885, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c" + ], + "birth_scan_exhausted": false + }, + "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu": { + "address": "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1783033845, + "birth_edge": { + "sink": "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 1171377350, + "signature": "5T6rTfTX8JKsNiXf9PCnAEL4w7HLzJfsVBeQwsTRcsuK9TyxUV9ym5MPTpVBepapLkDMQeTPdAEigW14azAfdiT8", + "slot": 430402156, + "block_time": 1783033845, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2": { + "address": "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2014 + }, + "first_seen": 1728020846, + "birth_edge": { + "sink": "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 203950000, + "signature": "5U8Sgir6egiAF84ZZW13rDa1ek69yJpBcTmv227D88J6Gbm69bF6Dq7WKoLSdYEeaeXGPSCmN76jDFXzZEkeQh2Z", + "slot": 293595031, + "block_time": 1728020846, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE": { + "address": "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784934452, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA" + ], + "birth_scan_exhausted": false + }, + "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1": { + "address": "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7196 + }, + "first_seen": 1728755528, + "birth_edge": { + "sink": "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 839200000, + "signature": "2DdKCofnd53ZCijpwVNM4xRHmTXYT8YqeRh99etQB4J3YmX48b1sNn6vq2RGrqtmsgbKq4jH1jHc9DnoSctSurUw", + "slot": 295234119, + "block_time": 1728755528, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG": { + "address": "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4316 + }, + "first_seen": 1743001899, + "birth_edge": { + "sink": "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "sources": [ + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "value": 200000000, + "signature": "3Rg9fS58hDfbMcnKWU6jeJXUva4g1jetwoNwvp7YiiH1GXDadsF7ZqxutWpxq6VXuHdRWpq61TFtQMs6KRhrkmqr", + "slot": 329323260, + "block_time": 1743001899, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "funded": [ + "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7" + ], + "birth_scan_exhausted": false + }, + "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9": { + "address": "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1773 + }, + "first_seen": 1699496374, + "birth_edge": { + "sink": "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 3442545653, + "signature": "Pvpzz5cpDfRekWPtKKux8YPWQuzJhsEonx1YpQLx9HkBiWkknk4Cwj6dyvof1ha8fnmrEkZqGAefpquu6G6HUDD", + "slot": 228889688, + "block_time": 1699496374, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2": { + "address": "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 163 + }, + "first_seen": 1770880341, + "birth_edge": { + "sink": "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1178878050, + "signature": "3Yn2VJBxAgpBmQ8XD5EXEPEdPfyYuvrRfiJEFCMn4JS3CwYURPu7QTwjsz9Masn96rwjNriemwBLyzFe8ZVzLwbq", + "slot": 399714845, + "block_time": 1770880341, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE": { + "address": "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784782106, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV" + ], + "birth_scan_exhausted": false + }, + "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA": { + "address": "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1783307590, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq": { + "address": "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1705 + }, + "first_seen": 1777229947, + "birth_edge": { + "sink": "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq", + "sources": [ + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + ], + "value": 3615443530, + "signature": "ev9FqbVk8jCJiy27MypNycKFYYeRJJJCpH6WiNPM5buomXGKnTHqz9YL5heguK76dncGDd8KnEMvKruGMzQFDRc", + "slot": 415842709, + "block_time": 1777229947, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9WL2A89YBr6X47ABKYNzPentWiBA3H8tpaiuf5CaYHx6" + ], + "funded": [ + "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV" + ], + "birth_scan_exhausted": false + }, + "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs": { + "address": "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 302 + }, + "first_seen": 1774289101, + "birth_edge": { + "sink": "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "sources": [ + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS" + ], + "value": 1406603230, + "signature": "58WsdGGRCSKky5UXVe4QCaZpALbxjqfksCdHYZUj6HtNo5GmE3SwBi7tfun9dovyXN7CHVXt56c7wc28deCM4NKb", + "slot": 408377546, + "block_time": 1774289101, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL": { + "address": "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 363 + }, + "first_seen": 1763311616, + "birth_edge": { + "sink": "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL", + "sources": [ + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS" + ], + "value": 128226803, + "signature": "5eRiYettfDeQJtbPPvuTnLYDt5fpqjhqxut1xMhVUk3za3oCNTFqNvBAtDwNCWxsWrPrKRQXj5xeECz2VjpFq6zd", + "slot": 380496679, + "block_time": 1763311783, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AF6B9C43LrVEZLSNdoVXuWeXZBM3voyQgykCxRRoUxqG", + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS" + ], + "funded": [ + "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj" + ], + "birth_scan_exhausted": false + }, + "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq": { + "address": "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1758453103, + "birth_edge": { + "sink": "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "sources": [ + "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi" + ], + "value": 1111260006, + "signature": "2wqEciaCd8SdPS1BaEWBxZfVTrHk3sMS7PZB8LobFDQA7e49g7awaW44rGj2gRiWbCoU4rW4SWbn7qCpQtPXaetx", + "slot": 368284051, + "block_time": 1758453103, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i": { + "address": "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1753315217, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb": { + "address": "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 253 + }, + "first_seen": 1776516090, + "birth_edge": { + "sink": "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "sources": [ + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1" + ], + "value": 268000000, + "signature": "3KB7X9tCYoBd3HrsMzGTYQ1avb3ceSiXpHuQv6qQ9LPLKVnxVoUaXdBeKF5qLp71mMTNmHXsNcKe3Hbd4JYs29C8", + "slot": 414039567, + "block_time": 1776516090, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z": { + "address": "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1230 + }, + "first_seen": 1769712791, + "birth_edge": { + "sink": "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z", + "sources": [ + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG" + ], + "value": 159000000, + "signature": "37ERW5HvvBbZ7xPSZK8kHSjpshWttD8PGTqwnw4hrmou8DyqToYBSU1oV1YqHfEdVbqBkkLULdg7VUmuZBsNkVZG", + "slot": 396754483, + "block_time": 1769712791, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG" + ], + "funded": [ + "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG" + ], + "birth_scan_exhausted": false + }, + "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf": { + "address": "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 180 + }, + "first_seen": 1745992468, + "birth_edge": { + "sink": "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "sources": [ + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH" + ], + "value": 2000000000, + "signature": "5ZbHXjZUDRS32uKijFGaJzvbiKBMYLHWcuES7gteaUuErcDjefnt9jsanTDhirv5Rs6JdCv9JbvLrNPPY29kLvEv", + "slot": 336839074, + "block_time": 1745993216, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX": { + "address": "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "owner": null, + "executable": false, + "signatures": { + "Exact": 166 + }, + "first_seen": 1604753907, + "birth_edge": { + "sink": "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 4990000000, + "signature": "5fNviBm3ujhH54CxhFVkMHGhBh3ycHu52naQLLfeipzWZXT5HXfNUswH9ovncyDCLMGWuFTkc8ZfSGwZRZKuFm67", + "slot": 47336493, + "block_time": 1604753907, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [ + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E" + ], + "birth_scan_exhausted": false + }, + "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv": { + "address": "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 49 + }, + "first_seen": 1778543921, + "birth_edge": { + "sink": "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1999000000, + "signature": "2577uZmHHHp14Cdos1o2FWPAe3yv8JQWbbDZ2MXz7UPeYsivzQKF7BxbnhK3x4ACnD4J4HUSi9URYrAfqLY5Et7T", + "slot": 422526013, + "block_time": 1779894352, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx": { + "address": "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 639 + }, + "first_seen": 1681873953, + "birth_edge": { + "sink": "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "sources": [ + "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK", + "2AdZQmGikAMWahuJRb27PGABQyF6iyQ8aUUYyDDwRRG6" + ], + "value": 1390880, + "signature": "4UjTtnFx1zxwZ3HfMqZJipdoHQ5Gcjujq9gJgoYaraMkeuRfhb3DhbJnFA7aRpYipMV3rWuBNdJG51K4m1gXs5Qj", + "slot": 189363909, + "block_time": 1681936762, + "ambiguous_attribution": true + }, + "fee_payers": [ + "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw": { + "address": "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq" + ], + "birth_scan_exhausted": false + }, + "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R": { + "address": "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9 + }, + "first_seen": 1784990514, + "birth_edge": { + "sink": "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 10091911, + "signature": "8noYJ5aCs9N6rbVBKCGMwVfVeCg1GQHGXhaNHP2rVcBZSMgbgiUpoNtpQ8XkASe78iZs3JqGk3c178jbeFgdrzc", + "slot": 435145890, + "block_time": 1784990514, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS": { + "address": "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784779900, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS", + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ" + ], + "birth_scan_exhausted": false + }, + "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi": { + "address": "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq" + ], + "birth_scan_exhausted": false + }, + "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K": { + "address": "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 483 + }, + "first_seen": 1756671400, + "birth_edge": { + "sink": "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "sources": [ + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL" + ], + "value": 100000000, + "signature": "5K8mBvSCxvxNt81ymbqjhH5Umde57URqC9pVdVzczT8JpRrPrKoEo5t8xEqBLWW2N74Fi5vNNKiCVKbBZZvLu8Gq", + "slot": 372063796, + "block_time": 1759948805, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "Hc2vsrdknaVuSypGNyMDuzR3FN3yWVcSuFALT6j2Cq71", + "BeVVXuvvGNmFBeK1bazfc2CYdvvJ5AFi4aNv75Ah7vo8", + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HK2mrz7pr8x9e4J7Bh1rsd6YidBWNk72fR3LUGpdy3hu": { + "address": "HK2mrz7pr8x9e4J7Bh1rsd6YidBWNk72fR3LUGpdy3hu", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T" + ], + "birth_scan_exhausted": false + }, + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1": { + "address": "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 653 + }, + "first_seen": 1773754017, + "birth_edge": { + "sink": "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1", + "sources": [ + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf" + ], + "value": 128164040, + "signature": "2PwhcVHygfgnrC6dUDdkyZG6XQq8QzBS5PY4ar7MKwUmnCWtXhhVYACkh8goKM7MozqdANUYs4MhnSVagFBJt7kn", + "slot": 407022368, + "block_time": 1773754017, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf" + ], + "funded": [ + "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv" + ], + "birth_scan_exhausted": false + }, + "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw": { + "address": "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1783107306, + "birth_edge": { + "sink": "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 9000000, + "signature": "4B1byRx7pvEAt5NfXDUwEqo2TyMbm1qFV1Qakd6tnjmjfUHQ6ThQJFpcN129xXoS29GPPtvScVyHGBkCV6kd9s2m", + "slot": 430584264, + "block_time": 1783107306, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh": { + "address": "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 322 + }, + "first_seen": 1761374606, + "birth_edge": { + "sink": "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "sources": [ + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" + ], + "value": 25732740, + "signature": "3AhoNZZLvSQ5gNfWsTeRkjLi8W9t1P63EPXD3YKxErjnAP37U44njwCmpBaygX1xi5fgUNHHSZaeMiEJq9F2jrHi", + "slot": 375651141, + "block_time": 1761374606, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16": { + "address": "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1781755484, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq" + ], + "birth_scan_exhausted": false + }, + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V": { + "address": "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1582 + }, + "first_seen": 1700759478, + "birth_edge": { + "sink": "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "sources": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "value": 951634000, + "signature": "5vBY9n8k1qYPBcapzouiPJ9whnVFx18Y9Y574PJfDDwMNKt59QoCsGbRgke24y6T3zTyjNNRqEzCnJUAwfWGNeE7", + "slot": 231795042, + "block_time": 1700759478, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "funded": [ + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP" + ], + "birth_scan_exhausted": false + }, + "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n": { + "address": "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 135 + }, + "first_seen": 1780765625, + "birth_edge": { + "sink": "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "sources": [ + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP" + ], + "value": 271700000000, + "signature": "5R2ekhoYBR8fWYnM27srHHH24YNpF79Bu2uws2dh3otMMSt7Kku9tgV5NUx2yFN53YjfhZ8iDv6zNzFJpuoZgyg2", + "slot": 424718918, + "block_time": 1780765625, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6": { + "address": "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 337 + }, + "first_seen": 1719988520, + "birth_edge": { + "sink": "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6", + "sources": [ + "RBNV3esFPQ4jAkzW9hzxtZj34vJ9Snzrm6arXLs1mYZ" + ], + "value": 669445618, + "signature": "2xwdfPkgWLvHhKL2f1su7F6qyRR7LgaNsYiakS8wpTpeoMc89PLbARf8HR29phRhtdRyRbkj6iWbfbB6KVa68pZX", + "slot": 275394508, + "block_time": 1719988520, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7FfB2zQRYUQwpPzkRxAeg2mCBGeCRKp4PCEeULJA9xTo" + ], + "funded": [ + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh" + ], + "birth_scan_exhausted": false + }, + "RBNV3esFPQ4jAkzW9hzxtZj34vJ9Snzrm6arXLs1mYZ": { + "address": "RBNV3esFPQ4jAkzW9hzxtZj34vJ9Snzrm6arXLs1mYZ", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6" + ], + "birth_scan_exhausted": false + }, + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN": { + "address": "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 104 + }, + "first_seen": 1757553631, + "birth_edge": { + "sink": "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN", + "sources": [ + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu" + ], + "value": 823856392, + "signature": "43aFuJ62SGgRmkXfrQfgydDDuYgrbTzLoNbZ4LD3afHdZyjd9SnUv9o64bxryezdAGxHpBVUt3Eyj8eSeYKtiCiS", + "slot": 366017478, + "block_time": 1757556380, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu" + ], + "funded": [ + "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC" + ], + "birth_scan_exhausted": false + }, + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu": { + "address": "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784721217, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt" + ], + "birth_scan_exhausted": false + }, + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV": { + "address": "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784714858, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri" + ], + "birth_scan_exhausted": false + }, + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4": { + "address": "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1777643790, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm" + ], + "birth_scan_exhausted": false + }, + "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK": { + "address": "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1723294092, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx" + ], + "birth_scan_exhausted": false + }, + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ": { + "address": "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 21 + }, + "first_seen": 1709918366, + "birth_edge": { + "sink": "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 1696237745, + "signature": "qdFZbtFQnSrYx5CCo78yqoFY1ZYvhKgUL5gPeKiiG1LJ2TG2jC4yKtUdwidZiH5tZZ2bt4HEqU4pfjBbMeDYJNc", + "slot": 252918864, + "block_time": 1709918366, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [ + "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf" + ], + "birth_scan_exhausted": false + }, + "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28": { + "address": "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 98 + }, + "first_seen": 1780986875, + "birth_edge": { + "sink": "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "sources": [ + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c" + ], + "value": 30000000, + "signature": "5RkppwfwvNhhTswurZaM4gwPQiDaie82UhBdHt8mHJSES3ci8cmDuHHRLYXoFuWD7KdurykQXq7dUBfaBw8HAiqW", + "slot": 425274321, + "block_time": 1780986875, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm": { + "address": "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1751863543, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK" + ], + "birth_scan_exhausted": false + }, + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV": { + "address": "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12 + }, + "first_seen": 1619359279, + "birth_edge": { + "sink": "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 31483630200, + "signature": "f6cQSbVaFKTPqNogr3WADjYPSr8ZgwcvYK5uaJiAP9A63W7WZgCjcDoGHxZWK8fdAVBqJzsjF43XYsuCG42fsNX", + "slot": 75325833, + "block_time": 1619359279, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [ + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi" + ], + "birth_scan_exhausted": false + }, + "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w": { + "address": "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 24000 + }, + "first_seen": 1784364506, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq" + ], + "birth_scan_exhausted": false + }, + "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ": { + "address": "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 34 + }, + "first_seen": 1784808222, + "birth_edge": { + "sink": "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "sources": [ + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv" + ], + "value": 100000000, + "signature": "3XFtWHvCubsszFNB4U9XX592rAbYqdKaUjy7KPTjxKmcAUziERiSHsPLvbSozHGgAgiZjZmdStGTB99ASeYB8GPU", + "slot": 434711620, + "block_time": 1784808222, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv" + ], + "funded": [], + "birth_scan_exhausted": false + } + } +} \ No newline at end of file diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index 8733cb3b..e5a933fa 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -378,6 +378,83 @@ The under-sampling flag still fires, and Run 4's finding about the tail survives the fix: coverage remains near 0.65 with Chao1 estimating 108 classes against 23 observed. +### Run 7 — the control, re-collected + +Helius, archival probes passed. 1,887 calls, 387 seconds. + +``` +attempted 92 | resolved 38 | evidence-unresolved 0 | budget-unresolved 54 + | scope-unresolved 0 | rpc failures 0 (0.00%) +``` + +| | Run 5 (broken) | Run 7 (fixed) | +|---|---|---| +| resolved | 16 | **38** | +| **evidence-unresolved** | 35 | **0** | +| `NoIncomingEdge` | **36** | **0** | +| ρ (point) | 0.0743 | 0.0362 | +| classes observed | 14 | 30 | +| Good–Turing coverage | 0.25 | 0.37 | + +The bug bit the control more than twice as hard, which is what surfaced it: +36 of 92 members were reported as having no funding at all, and every one was +wrong. Resolution more than doubled. + +**Run 7 still refuses.** 38 of 92 is 41%, under the informativeness gate's half. +The bracket spans 0.0124 … 0.1289, a tenfold range. + +### The comparison, and why it is refused + +``` +population members rho resampled 2.5-97.5% bias +privacy pool 54 0.0955 0.0848 .. 0.1790 +0.0276 +staking control 38 0.0362 0.0463 .. 0.0708 +0.0203 + +difference (privacy pool − staking control): +0.0592 95% +0.0259 .. +0.1229 +``` + +**The interval on the difference excludes zero.** Taken at face value that says +the privacy pool is the more provenance-concentrated population — which is the +direction that supports this project's entire argument. + +**`compare` refuses to report it**, because the control resolves fewer than half +its members. The reasoning is not a formality: + +> What survives after the unresolved are dropped is each population's *traceable* +> subset, and traceability is not independent of provenance class. A wallet funded +> straight from an exchange resolves in one hop; a wallet funded through fresh +> intermediaries exhausts the budget. So the resolved subsets of two populations +> can differ in provenance concentration purely because the *unresolved* ones were +> different, and nothing in the numbers above would reveal it. + +This gate did not exist when `compare` was written — it checked the RPC-failure +gate and not the informativeness one, which was an inconsistency between the two +commands. It was added on finding that the control fell on the wrong side of it, +and the honest way to say that is plainly: **we built the check that refuses our +own favourable result, after seeing that the result was favourable.** The code +and this paragraph are both in the repository; a reader can weigh that as they +see fit. + +**What would close it** is resolution above half on the control — 46 of 92, from +38 — which is a bigger traversal budget, not a different metric. 54 of its 92 +members are budget-unresolved, so the headroom exists. It is not attempted here, +because Run 7's pre-registered stopping rule was one collection per frame, and a +stopping rule that bends when the result is close is not a stopping rule. + +### What the two runs establish, and what they do not + +**Established.** The tracer bug was real, symmetric across populations, and it is +fixed: the evidence-unresolved bucket went to zero in both. The corrected +headline is ρ = 0.0955. The comparison machinery exists, is tested, and refuses +correctly in two different ways — it declines to separate Runs 3 and 4, which are +one pool at two budgets, and it declines to rank two populations when one is +under-resolved. + +**Not established.** That a privacy pool's depositors are more +provenance-concentrated than ordinary users. The point estimates say so and the +sample does not support saying it. `ρ`'s comparability across populations remains +**demonstrated as machinery and unproven as a finding.** + ## What we do not conclude **Nothing about how private that pool is, and the headline does not become that From 38e6e0d3c106b2a7b9d22196b4d259e4dbef28e5 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 02:40:53 -0300 Subject: [PATCH 46/83] docs: the control comparison in the README, refusal and all --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index f794fadd..04f5c845 100644 --- a/README.md +++ b/README.md @@ -253,6 +253,38 @@ what neither absolute number does — and when the interval on the difference contains zero, the tool says the two are indistinguishable and declines to rank them. +### The comparison we ran, and refused + +`ρ` is the headline because it is comparable across populations, so we measured +one that is **not seeking privacy at all** — Marinade staking depositors, +identical pipeline, identical parameters — to give the number a scale. + +``` +population members rho resampled 2.5-97.5% bias +privacy pool 54 0.0955 0.0848 .. 0.1790 +0.0276 +staking control 38 0.0362 0.0463 .. 0.0708 +0.0203 + +difference: +0.0592 95% +0.0259 .. +0.1229 +``` + +The interval on the difference excludes zero, and it points the way this +project's argument wants: the privacy pool reads as the more concentrated +population. **The tool refuses to report it**, because the control resolves 38 +of 92 members and what survives is its *traceable* subset — and traceability is +not independent of provenance class. A wallet funded straight from an exchange +resolves in one hop; one funded through fresh intermediaries exhausts the +budget. That selection alone can manufacture the difference. + +The gate that refuses this did not exist when `compare` was written, and was +added after seeing the control fall on the wrong side of it. So: **we built the +check that refuses our own favourable result, after learning the result was +favourable.** Both the code and that sentence are in the repository. + +What it would take is resolution above half on the control — a bigger traversal +budget, not a different metric. It is not attempted, because Run 7's stopping +rule was one collection per frame, and a stopping rule that bends when the +result is close is not one. + ### The folklore formula is inverted `2^H(C)` — entropy over the class-size distribution — is widely quoted as the @@ -312,6 +344,10 @@ multi-party ceremony, not more SOL. verify the key, and nobody can regenerate it either. - The on-chain `k_floor` bounds **program-visible membership** only. That is all a program can check. +- **Not that privacy pools attract more concentrated funding than ordinary + users.** We measured a control to find out, the point estimates say they do, + and the sample does not support saying it. `ρ`'s comparability across + populations is demonstrated as machinery and unproven as a finding. - Not audited. `docs/MEASUREMENT_LOG.md` records every collection run, including the one that From facf5302b266716e7318e400f6db315fb7ce0716 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 02:53:18 -0300 Subject: [PATCH 47/83] measure: test the missing-at-random assumption instead of hoping for it Every unresolved bracket and every cross-population comparison in this project rests on an assumption nobody had checked: that the members which resolve are a fair draw of the classes present. If members that are cheap to trace are systematically exchange-funded and expensive ones are systematically something else, the resolved subset is not the population, and a difference between two such subsets can be selection and nothing else. That was the stated reason for refusing the control comparison, and it was a suspicion rather than a finding. It is testable with data already collected. Given one frame at two budgets, split the larger run's resolved members into those the smaller run also reached and those only the larger one did, and ask whether the two groups carry the same class distribution. Both populations say no selection at this margin: control easy 16 rho 0.0743 | hard 22 rho 0.0585 | diff 95% -0.0144..+0.0786 privacy easy 39 rho 0.1179 | hard 15 rho 0.1250 | diff 95% -0.1507..+0.0704 Neither separates. At the margin these budgets test, being resolvable is not picking out particular provenance classes. Evidence, not proof, and the command says so: it speaks for the members just beyond the cheaper budget and cannot speak for those beyond the larger one. Also makes birth_scan_cap default on deserialisation. Samples committed before that field existed had stopped loading, which would have quietly retired the evidence behind every earlier published number -- the opposite of what a committed artifact is for. Old samples now report the budget they were actually collected under, which was effectively one transaction. --- crates/mirror-cli/src/main.rs | 134 ++++++++++++++++++++++++++ crates/mirror-provenance/src/trace.rs | 14 +++ 2 files changed, 148 insertions(+) diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 81666b63..81a1e597 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -148,6 +148,29 @@ enum Command { #[arg(long, default_value = "baseline")] against_label: String, }, + /// Tests whether *being resolvable* is correlated with provenance class. + /// + /// This is the assumption every unresolved bracket and every cross-population + /// comparison quietly rests on. Dropping unresolved members is only harmless + /// if the members that resolve are a fair draw of the classes present. If + /// easy-to-trace members are systematically exchange-funded and hard ones are + /// systematically something else, then the resolved subset is not the + /// population, and a difference between two such subsets can be pure + /// selection. + /// + /// Given the same frame collected at two budgets, it splits the larger run's + /// resolved members into those the smaller run also resolved and those only + /// the larger one reached, and asks whether those two groups have the same + /// class distribution. **Separation is bad news** — it is evidence that the + /// unresolved are not missing at random. + Selection { + /// The smaller-budget collection. + #[arg(long)] + earlier: PathBuf, + /// The larger-budget collection of the same frame. + #[arg(long)] + later: PathBuf, + }, /// Runs the whole lifecycle against a live cluster and prints every /// signature, so the result is checkable rather than asserted. Soak { @@ -183,6 +206,27 @@ fn vk_digest(vk: &mirror_circuit::SolanaVerifyingKey) -> String { hex::encode(hasher.finalize()) } +/// Loads a committed sample and returns each seed's class label, where it +/// reached one. +/// +/// Keyed by seed rather than flattened, so two collections of the same frame can +/// be aligned member by member. +fn labels_by_seed(path: &std::path::Path) -> Result> { + let text = + std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?; + let sample = mirror_provenance::Sample::from_json(&text)?; + let (results, _) = mirror_provenance::classify_sample( + &sample, + &mirror_provenance::AnchorSet::default(), + &mirror_provenance::Thresholds::default(), + sample.manifest.collected_at, + ); + Ok(results + .into_iter() + .filter_map(|(seed, o)| o.label().map(|l| (seed, l.to_string()))) + .collect()) +} + /// Loads a committed sample and returns one class label per resolved member. /// /// Applies the same failure gate `analyze` does. A comparison drawn from a run @@ -703,6 +747,96 @@ fn main() -> Result<()> { ); Ok(()) } + Command::Selection { earlier, later } => { + let before = labels_by_seed(&earlier)?; + let after = labels_by_seed(&later)?; + + // Members the larger budget reached, split by whether the smaller + // one reached them too. + let mut easy: Vec = Vec::new(); + let mut hard: Vec = Vec::new(); + for (seed, label) in &after { + if before.contains_key(seed) { + easy.push(label.clone()); + } else { + hard.push(label.clone()); + } + } + + println!("earlier {} resolved", before.len()); + println!( + "later {} resolved ({} of them newly)", + after.len(), + hard.len() + ); + println!(); + + if hard.len() < 5 { + println!( + "Only {} members were newly resolved. That is too few to say anything \ + about\nwhether resolvability selects on class, and this check reports \ + nothing rather\nthan reporting a number computed from it.", + hard.len() + ); + return Ok(()); + } + + let reps = mirror_provenance::bootstrap::DEFAULT_REPLICATES; + let seed = mirror_provenance::bootstrap::DEFAULT_SEED; + let ie = mirror_provenance::loss_factor_interval(&easy, reps, seed) + .ok_or_else(|| anyhow::anyhow!("no easily-resolved members"))?; + let ih = mirror_provenance::loss_factor_interval(&hard, reps, seed) + .ok_or_else(|| anyhow::anyhow!("no newly-resolved members"))?; + + println!("group members rho resampled 2.5-97.5%"); + println!( + "resolved at both budgets {:>7} {:.4} {:.4} .. {:.4}", + easy.len(), + ie.point, + ie.lo, + ie.hi + ); + println!( + "only at the larger {:>7} {:.4} {:.4} .. {:.4}", + hard.len(), + ih.point, + ih.lo, + ih.hi + ); + + let d = mirror_provenance::difference_interval(&easy, &hard, reps, seed) + .ok_or_else(|| anyhow::anyhow!("nothing to compare"))?; + println!(); + println!( + "difference (easy − hard): {:+.4} 95% {:+.4} .. {:+.4}", + d.point, d.lo, d.hi + ); + println!(); + + if d.excludes_zero() { + println!( + "SELECTION DETECTED. Members that only a larger budget reaches have a\n\ + measurably different class distribution from those any budget reaches.\n\n\ + The unresolved are therefore NOT missing at random, and dropping them is\n\ + not neutral: the resolved subset of a population is biased toward whichever\n\ + classes happen to be cheap to trace. Every unresolved bracket in this\n\ + project is still a valid bound, but no comparison between two populations\n\ + at different resolution rates can be trusted, and more budget does not fix\n\ + that — it moves the boundary without removing it." + ); + } else { + println!( + "NO SELECTION DETECTED at this margin. The members that needed a larger\n\ + budget carry a class distribution indistinguishable from those that did\n\ + not, so at this margin resolvability is not picking out particular\n\ + provenance classes.\n\n\ + This is evidence, not proof. It says the members just beyond the cheaper\n\ + budget look like the ones inside it; it cannot speak for members beyond\n\ + the larger budget too, and a heavier tail could still be hiding there." + ); + } + Ok(()) + } Command::Soak { program, url, diff --git a/crates/mirror-provenance/src/trace.rs b/crates/mirror-provenance/src/trace.rs index 4e910978..aa93c46c 100644 --- a/crates/mirror-provenance/src/trace.rs +++ b/crates/mirror-provenance/src/trace.rs @@ -84,9 +84,23 @@ pub struct CollectionConfig { /// So the walk continues forward from the oldest until a credit appears. /// Exhausting this budget without finding one is a **budget** outcome and is /// recorded as such, never as evidence about the address. + /// + /// Defaulted on deserialisation so that samples committed before this field + /// existed still load. A committed artifact is the whole point of the + /// two-pass split — anyone must be able to recompute a published number from + /// it years later — and a schema change that silently makes old samples + /// unreadable would quietly retire the evidence for every earlier claim. + #[serde(default = "default_birth_scan_cap")] pub birth_scan_cap: u32, } +/// What runs before this field existed effectively did: read exactly one +/// transaction and stop. Recorded honestly rather than backfilled with today's +/// value, so an old sample reports the budget it was actually collected under. +fn default_birth_scan_cap() -> u32 { + 1 +} + impl Default for CollectionConfig { fn default() -> Self { CollectionConfig { From 3b85ffd77f6920ae334b53641bce8c8bec0a1608 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 02:54:03 -0300 Subject: [PATCH 48/83] docs: pre-register Run 8, and disclose why it is the weakest pre-registration here --- docs/MEASUREMENT_LOG.md | 60 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index e5a933fa..d25402f3 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -455,6 +455,66 @@ provenance-concentrated than ordinary users. The point estimates say so and the sample does not support saying it. `ρ`'s comparability across populations remains **demonstrated as machinery and unproven as a finding.** +## Run 8 — pre-registered, and the weakest pre-registration in this document + +Committed before the collection started, like Runs 4 to 7. It needs a disclosure +the others did not. + +**The problem with this run, stated first.** Run 7's comparison already told us +the direction: the difference favours the privacy pool, and the only thing +standing between that and a publishable finding is the control's resolution rate. +So this run is being made **with knowledge of which outcome would suit us**, which +is exactly the condition pre-registration exists to neutralise and cannot fully +neutralise here. Runs 4 and 6 did not have that problem; this one does. + +What can be done about it is done: the budget and the stopping rule are fixed in +advance, this is the final run of the programme whatever it returns, and this +paragraph is committed alongside. What cannot be done about it is the knowledge +itself. A reader should discount this run relative to the others, and the reason +is written here rather than left for them to notice. + +**Why it is worth running anyway.** The refusal in Run 7 rested on a suspicion — +that resolvability correlates with provenance class — and that suspicion has since +been **tested and not supported**, on both populations, by the selection check +above. The specific objection that blocked the comparison is now the objection +with evidence against it, so raising resolution is no longer papering over a +confound; it is removing the one gate that remains. + +**Frame.** The same 100 Marinade depositors as Runs 5 and 7. Not re-drawn. + +**What changes, and only this.** 87 of 92 chains in Run 7 ended at the signature +page cap. That is the binding constraint and the only one being moved. + +| parameter | Run 7 | Run 8 | +|---|---|---| +| signature page cap | 24 | **48** | +| depth | 16 | 16 | +| birth scan | 24 | 24 | + +Depth is unchanged because it was not binding: Run 7 had zero depth-exceeded +outcomes. + +**Prediction, recorded in advance.** Resolution rises above the 46 needed, but not +by much — the page cap is being doubled against a population where nearly every +chain hit it, so the marginal member is expensive. ρ for the control most likely +falls further, because the members still missing are the ones with the longest +funding chains and those are the least likely to share a class with anyone. + +If the control's ρ falls, the difference widens and the comparison separates. **We +are predicting the result that suits us, having already seen a version of it**, +and it is on the record so that a reader can hold us to it either way. + +**Stopping rule, absolute.** This is the last collection in this document. If the +control still resolves below half, the comparison is reported as unachieved and +stays unachieved. If it resolves above half and the difference fails to separate, +that is reported as the finding. No Run 9 under any outcome. + +**Also pre-specified:** the selection check will be run again at the new margin +(Run 7 → Run 8). If it *does* detect selection there, the comparison is refused +regardless of the resolution rate, because that would mean the members this run +added are not a fair draw and the extra resolution bought nothing but a +better-looking denominator. + ## What we do not conclude **Nothing about how private that pool is, and the headline does not become that From e3472fa03f67d3f1885589de97b5e7f69b8e8a74 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 03:01:30 -0300 Subject: [PATCH 49/83] docs: the selection check in the README and the method --- README.md | 22 ++++++++++++++++++++++ docs/PROVENANCE_METHOD.md | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) diff --git a/README.md b/README.md index 04f5c845..9fed3d79 100644 --- a/README.md +++ b/README.md @@ -176,6 +176,7 @@ mirror seeds --program # member-weighted frame, one row per depo mirror collect --seeds seeds.txt # the only networked step; writes sample.json mirror analyze --sample sample.json # pure, offline, deterministic mirror compare --sample a.json --against b.json # is the difference real? +mirror selection --earlier lo.json --later hi.json # are the unresolved missing at random? ``` `data/sample-privacycash-run6.json` is the committed artifact behind the @@ -253,6 +254,27 @@ what neither absolute number does — and when the interval on the difference contains zero, the tool says the two are indistinguishable and declines to rank them. +### The assumption underneath all of it, tested + +Dropping unresolved members is only harmless if the ones that resolve are a fair +draw of the classes present. Every bracket and every comparison rests on that, +and it is normally asserted and left alone. It is testable: collect one frame at +two budgets, split the resolved members into *cheap to trace* and *expensive to +trace*, and ask whether the two groups have the same class distribution. + +| population | cheap | expensive | difference, 95% | +|---|---|---|---| +| staking control | 16, ρ 0.0743 | 22, ρ 0.0585 | −0.0144 … +0.0786 | +| privacy pool | 39, ρ 0.1179 | 15, ρ 0.1250 | −0.1507 … +0.0704 | + +Neither separates: at this margin, being resolvable does not pick out particular +provenance classes. Evidence, not proof — it speaks for the members just beyond +a cheaper budget, not for those beyond the larger one. + +Had it separated, that would have been the more important result, and it would +have invalidated the cross-population comparison outright rather than merely +delaying it. + ### The comparison we ran, and refused `ρ` is the headline because it is comparable across populations, so we measured diff --git a/docs/PROVENANCE_METHOD.md b/docs/PROVENANCE_METHOD.md index 065a4d31..2ee0eb6b 100644 --- a/docs/PROVENANCE_METHOD.md +++ b/docs/PROVENANCE_METHOD.md @@ -345,6 +345,45 @@ two samples of the same underlying shape always differ by *something*. The unit test that matters here is not that a stark difference separates — that is easy. It is that two identically shaped populations do **not**. +### 2.6b Testing the assumption the bracket rests on + +The unresolved bracket bounds what the unresolved members *could* have been. Every +number computed from the resolved ones alone additionally assumes those resolved +members are a **fair draw of the classes present** — that resolvability is +independent of provenance class. + +That assumption is usually stated and left there. It is testable. + +**The design.** Collect one frame at two budgets. Partition the larger run's +resolved members into + +- those the smaller budget also reached — *cheap to trace*, and +- those only the larger budget reached — *expensive to trace*, + +and bootstrap the difference in `ρ` between the two groups. + +**Separation is the bad outcome.** If expensive-to-trace members carry a different +class distribution from cheap ones, the unresolved are not missing at random, the +resolved subset is biased toward whichever classes happen to be cheap, and no +amount of extra budget repairs it — more budget moves the boundary without +removing it. Every cross-population comparison would then be uninterpretable. + +**Measured, both populations, on the runs in `MEASUREMENT_LOG.md`:** + +| population | cheap | expensive | difference, 95% | +|---|---|---|---| +| staking control | 16 members, ρ 0.0743 | 22 members, ρ 0.0585 | −0.0144 … +0.0786 | +| privacy pool | 39 members, ρ 0.1179 | 15 members, ρ 0.1250 | −0.1507 … +0.0704 | + +Neither separates. At the margin these budgets test, being resolvable is not +picking out particular provenance classes. + +**What that licenses, exactly.** It is evidence that the members *just beyond* a +cheaper budget resemble those inside it. It says nothing about members beyond the +larger budget, where a heavier tail could still sit, and it is a statement about +these two populations at these budgets rather than a general law. It is reported +as evidence, never as the assumption being discharged. + ### 2.7 Reference vectors for unit tests Exact values, computed at full double precision. Assert to 1e-9. From a3cf16e1bfb3fecd6e3fbf05cc86667a88a9d76e Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 03:13:18 -0300 Subject: [PATCH 50/83] measure: Run 8 fails its own prediction, and the comparison stays unachieved The pre-registration predicted a doubled page cap would lift the control above the 46 resolved members the informativeness gate needs. It reached 42. The prediction was made knowing which answer suited us, it is on the record, and it was wrong. So compare refuses a second time and this is final: Run 8's stopping rule was absolute before the collection started, and there is no Run 9. The difference still excludes zero and still points our way; the gate still holds it back. The selection check declined at the new margin -- only 4 members were newly resolved, under the 5 it requires -- and reported nothing rather than computing a number from four points. The earlier margin's check stands as the evidence. The failed run produced the better finding anyway. Doubling the budget bought four members for 1,246 calls: 311 calls each against an average of 50, a sixfold deterioration from one doubling. The control is not under-resolved because the budget was mean. Its remaining members have genuinely long funding chains -- and the staking pool, where nobody is seeking deniability, is markedly harder to trace than the privacy pool, which resolved at 65% for a third of the cost per member. Two explanations fit and this data cannot separate them: staking attracts older wallets with deeper histories, or privacy-pool depositors disproportionately fund straight from exchanges, which terminate in one hop. The second would sharpen our own concentration finding, which is why it is named as a candidate and not asserted. --- README.md | 28 +- data/sample-marinade-run8.json | 5612 ++++++++++++++++++++++++++++++++ docs/MEASUREMENT_LOG.md | 63 + 3 files changed, 5694 insertions(+), 9 deletions(-) create mode 100644 data/sample-marinade-run8.json diff --git a/README.md b/README.md index 9fed3d79..cbb0ad7c 100644 --- a/README.md +++ b/README.md @@ -291,21 +291,31 @@ difference: +0.0592 95% +0.0259 .. +0.1229 The interval on the difference excludes zero, and it points the way this project's argument wants: the privacy pool reads as the more concentrated -population. **The tool refuses to report it**, because the control resolves 38 -of 92 members and what survives is its *traceable* subset — and traceability is -not independent of provenance class. A wallet funded straight from an exchange -resolves in one hop; one funded through fresh intermediaries exhausts the -budget. That selection alone can manufacture the difference. +population. **The tool refuses to report it**, because the control resolves 42 +of 92 members and what survives is its *traceable* subset. The gate that refuses this did not exist when `compare` was written, and was added after seeing the control fall on the wrong side of it. So: **we built the check that refuses our own favourable result, after learning the result was favourable.** Both the code and that sentence are in the repository. -What it would take is resolution above half on the control — a bigger traversal -budget, not a different metric. It is not attempted, because Run 7's stopping -rule was one collection per frame, and a stopping rule that bends when the -result is close is not one. +Then we spent a run trying to clear it, having predicted in advance — in the +committed log, knowing which answer suited us — that a doubled budget would get +the control above half. **It did not.** Resolution went 38 → 42 against the 46 +needed, and the prediction is on the record as wrong. + +That failed run produced the more interesting number anyway: **311 RPC calls per +additional resolved member, against 50 for the population as a whole.** The +control is not under-resolved because we were stingy. Its remaining members have +genuinely long funding chains, and the *staking* pool — where nobody wants +deniability — turns out to be markedly harder to trace than the privacy pool, +which resolved at 65% for a third of the cost per member. We do not claim to know +why, and the explanation that would flatter us is one of the two candidates, +which is exactly why we are not asserting it. + +The comparison is reported as **unachieved**. There was no further run: the +stopping rule was fixed before collection, and a stopping rule that bends when +the result is close is not one. ### The folklore formula is inverted diff --git a/data/sample-marinade-run8.json b/data/sample-marinade-run8.json new file mode 100644 index 00000000..efd39ee9 --- /dev/null +++ b/data/sample-marinade-run8.json @@ -0,0 +1,5612 @@ +{ + "manifest": { + "endpoint": "https://mainnet.helius-rpc.com", + "first_available_block": 0, + "archival_probe_ok": true, + "collected_at": 1785045254, + "config": { + "scope": "Sol", + "depth_max": 16, + "min_edge_lamports": 500000, + "sig_page_cap": 48, + "page_size": 1000, + "birth_scan_cap": 24 + }, + "thresholds_hub_signatures": 5000, + "thresholds_distributor_fanout": 20, + "thresholds_cluster_min_size": 3, + "rpc_calls": 3133, + "excluded_non_wallet": [ + "71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip", + "DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8", + "DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv", + "Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK", + "DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN", + "3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi", + "83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg", + "7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd" + ], + "ambiguous_attribution_rate": 0.020134228187919462 + }, + "chains": [ + { + "seed": "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "visited": [ + "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "PageCapHit" + }, + { + "seed": "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "visited": [ + "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V", + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu" + ], + "stop": "PageCapHit" + }, + { + "seed": "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "visited": [ + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8", + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY", + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU", + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN", + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "visited": [ + "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "visited": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD", + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "stop": "PageCapHit" + }, + { + "seed": "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "visited": [ + "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" + ], + "stop": "PageCapHit" + }, + { + "seed": "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "visited": [ + "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "visited": [ + "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh", + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6", + "RBNV3esFPQ4jAkzW9hzxtZj34vJ9Snzrm6arXLs1mYZ" + ], + "stop": "LocalTerminal" + }, + { + "seed": "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "visited": [ + "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "stop": "PageCapHit" + }, + { + "seed": "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "visited": [ + "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw" + ], + "stop": "PageCapHit" + }, + { + "seed": "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "visited": [ + "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi" + ], + "stop": "LocalTerminal" + }, + { + "seed": "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "visited": [ + "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD", + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "stop": "PageCapHit" + }, + { + "seed": "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "visited": [ + "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c", + "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "visited": [ + "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw" + ], + "stop": "LocalTerminal" + }, + { + "seed": "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "visited": [ + "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ", + "visited": [ + "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ" + ], + "stop": "PageCapHit" + }, + { + "seed": "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "visited": [ + "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "visited": [ + "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4" + ], + "stop": "PageCapHit" + }, + { + "seed": "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "visited": [ + "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE", + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH", + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "stop": "PageCapHit" + }, + { + "seed": "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "visited": [ + "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm" + ], + "stop": "PageCapHit" + }, + { + "seed": "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "visited": [ + "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ", + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ", + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi", + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "visited": [ + "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i", + "visited": [ + "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i" + ], + "stop": "PageCapHit" + }, + { + "seed": "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq", + "visited": [ + "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq" + ], + "stop": "PageCapHit" + }, + { + "seed": "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "visited": [ + "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW", + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS" + ], + "stop": "PageCapHit" + }, + { + "seed": "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "visited": [ + "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK" + ], + "stop": "PageCapHit" + }, + { + "seed": "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "visited": [ + "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "visited": [ + "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn", + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq", + "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w" + ], + "stop": "PageCapHit" + }, + { + "seed": "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "visited": [ + "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "visited": [ + "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg", + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP", + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E", + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "visited": [ + "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "visited": [ + "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1" + ], + "stop": "PageCapHit" + }, + { + "seed": "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "visited": [ + "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r", + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J", + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "visited": [ + "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw" + ], + "stop": "PageCapHit" + }, + { + "seed": "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "visited": [ + "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn" + ], + "stop": "PageCapHit" + }, + { + "seed": "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "visited": [ + "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk", + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "stop": "PageCapHit" + }, + { + "seed": "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "visited": [ + "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "HK2mrz7pr8x9e4J7Bh1rsd6YidBWNk72fR3LUGpdy3hu" + ], + "stop": "LocalTerminal" + }, + { + "seed": "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "visited": [ + "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "stop": "PageCapHit" + }, + { + "seed": "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "visited": [ + "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "stop": "PageCapHit" + }, + { + "seed": "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "visited": [ + "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL" + ], + "stop": "PageCapHit" + }, + { + "seed": "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "visited": [ + "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "visited": [ + "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6", + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7", + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "stop": "PageCapHit" + }, + { + "seed": "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "visited": [ + "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "visited": [ + "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "visited": [ + "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "visited": [ + "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP", + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "stop": "PageCapHit" + }, + { + "seed": "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "visited": [ + "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4", + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "stop": "PageCapHit" + }, + { + "seed": "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA", + "visited": [ + "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA" + ], + "stop": "PageCapHit" + }, + { + "seed": "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z", + "visited": [ + "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z", + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "stop": "PageCapHit" + }, + { + "seed": "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "visited": [ + "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV", + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "stop": "PageCapHit" + }, + { + "seed": "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G", + "visited": [ + "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G" + ], + "stop": "PageCapHit" + }, + { + "seed": "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "visited": [ + "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "visited": [ + "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" + ], + "stop": "PageCapHit" + }, + { + "seed": "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "visited": [ + "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "stop": "PageCapHit" + }, + { + "seed": "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "visited": [ + "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z", + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG" + ], + "stop": "PageCapHit" + }, + { + "seed": "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "visited": [ + "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "visited": [ + "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "visited": [ + "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "visited": [ + "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "visited": [ + "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "visited": [ + "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL", + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "visited": [ + "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg", + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP", + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E", + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "visited": [ + "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "visited": [ + "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "visited": [ + "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "visited": [ + "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU" + ], + "stop": "PageCapHit" + }, + { + "seed": "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "visited": [ + "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "stop": "PageCapHit" + }, + { + "seed": "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "visited": [ + "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "visited": [ + "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq", + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + ], + "stop": "PageCapHit" + }, + { + "seed": "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "visited": [ + "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1", + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8", + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY", + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU", + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN", + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "visited": [ + "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep", + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri", + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "stop": "PageCapHit" + }, + { + "seed": "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "visited": [ + "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "visited": [ + "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW", + "4AcmsW4k1JKCRv9irityUMkJ8eMm1JSqG9rR5FcNdZvy" + ], + "stop": "PageCapHit" + }, + { + "seed": "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "visited": [ + "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "visited": [ + "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "stop": "PageCapHit" + }, + { + "seed": "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "visited": [ + "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN", + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu", + "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk" + ], + "stop": "PageCapHit" + }, + { + "seed": "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "visited": [ + "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" + ], + "stop": "PageCapHit" + }, + { + "seed": "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb", + "visited": [ + "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb", + "HFWv1riXSRJ3nVnnLZ9xi1K4r3zppRvcfVuiAGqAA8Y6" + ], + "stop": "PageCapHit" + }, + { + "seed": "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "visited": [ + "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "stop": "PageCapHit" + }, + { + "seed": "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "visited": [ + "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "stop": "PageCapHit" + }, + { + "seed": "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "visited": [ + "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS" + ], + "stop": "PageCapHit" + }, + { + "seed": "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "visited": [ + "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "visited": [ + "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "visited": [ + "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "visited": [ + "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "visited": [ + "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH", + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "stop": "PageCapHit" + }, + { + "seed": "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "visited": [ + "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ", + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "stop": "PageCapHit" + }, + { + "seed": "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ", + "visited": [ + "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ", + "EZA5v8GGTDkPohspz9sVP6gBaP2D7ji3MVLvEsowRcnu", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + }, + { + "seed": "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "visited": [ + "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm", + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL", + "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh", + "F3Jh7vot1xC4gmr4AjcJBKouHEej13KMyySUFk2CFmUc", + "HUGi35NLzi1Kch86pkdDJLpkX6dccj5vDjAKwP5SCTy", + "7tyVqmZ42VWKz9NcktM4drZQrypcnk73F33Brjnmtc88", + "7g35rQ6bsxgXrMuDqLoYXJFFwjKp6uJkZZujrnLRnTuW", + "BfqUSbXDfxKz99QdzfiiYeuUh5BzqvCVtTppV7DHy4cr", + "GbG7Kekbzjypekv9vEEdyERXp3XvpvAqhtFfvEUwrAb7" + ], + "stop": "BelowThreshold" + }, + { + "seed": "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "visited": [ + "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "stop": "PageCapHit" + }, + { + "seed": "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM", + "visited": [ + "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM" + ], + "stop": "BirthScanExhausted" + }, + { + "seed": "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "visited": [ + "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL", + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "stop": "PageCapHit" + } + ], + "facts": { + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm": { + "address": "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784474223, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi" + ], + "birth_scan_exhausted": false + }, + "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4": { + "address": "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 6457 + }, + "first_seen": 1756313602, + "birth_edge": { + "sink": "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4", + "sources": [ + "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V" + ], + "value": 103993966, + "signature": "55s64RhPGBNPTVDLsRnnXVpDJ5yKvuc5bW51GxXPJXNE3tRBWsSYt7nNovHNkQmP3WQD1s1hHS6RCXaec1ERULEK", + "slot": 363139506, + "block_time": 1756410719, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL": { + "address": "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL", + "owner": null, + "executable": false, + "signatures": { + "Exact": 81 + }, + "first_seen": 1736184345, + "birth_edge": { + "sink": "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL", + "sources": [ + "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh" + ], + "value": 3132408210, + "signature": "3GQxAV1idjVjzeuVGWgU2o4csbnbWrQK9VGHy8doD8WTcefK8y2VdnLuThnpUaMocGRhG2ErT86zPm5ZVMyqBQdT", + "slot": 312286142, + "block_time": 1736184345, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh" + ], + "funded": [ + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm" + ], + "birth_scan_exhausted": false + }, + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL": { + "address": "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1782776045, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck" + ], + "birth_scan_exhausted": false + }, + "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk": { + "address": "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk", + "owner": null, + "executable": false, + "signatures": { + "Exact": 6 + }, + "first_seen": 1783099753, + "birth_edge": { + "sink": "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk", + "sources": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "value": 2039280, + "signature": "Hhyx93TXt6SLEjwsgZs3xDHb1jMgZffgEhuwMLhYYFRNqnzVcmGbo5xPrz3CGRDXVXQd3HWNbLYk2Qs2bUsz7Uf", + "slot": 430565619, + "block_time": 1783099753, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [ + "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh" + ], + "birth_scan_exhausted": false + }, + "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh": { + "address": "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 80 + }, + "first_seen": 1752673276, + "birth_edge": { + "sink": "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh", + "sources": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "value": 12063418500, + "signature": "cKPChkvMEjtfwWt9C9UT6huX3cCp543m4ywRXBuBsuYWNKrDuvTCaJqSFFUym2mvuXMG1xCqWhQqvgUDjinFVJb", + "slot": 353692459, + "block_time": 1752673276, + "ambiguous_attribution": false + }, + "fee_payers": [ + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK": { + "address": "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7962 + }, + "first_seen": 1774268614, + "birth_edge": { + "sink": "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK", + "sources": [ + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ" + ], + "value": 5000000000, + "signature": "3D9VPk2WSdFidXqT2yc7QmN4so5tcRG8oq1RMMVN4Y81op9PcSrNPPhukqLvU2fmRPAncjyFp7csZxbee2uBg8qw", + "slot": 408325661, + "block_time": 1774268614, + "ambiguous_attribution": false + }, + "fee_payers": [ + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o": { + "address": "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 72 + }, + "first_seen": 1767875809, + "birth_edge": { + "sink": "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o", + "sources": [ + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW" + ], + "value": 50000000, + "signature": "jAurnGX1dLaqmmcS95zSb1N2XZHj65BdUNAtchot1MWmx3z6We5f8Jew1sGu9NrQFrWGR8QdkMCSWSUAfu1yahT", + "slot": 392132125, + "block_time": 1767875809, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7": { + "address": "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 33 + }, + "first_seen": 1778858822, + "birth_edge": { + "sink": "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7", + "sources": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG" + ], + "value": 10000000, + "signature": "41PgkwaTASYRwAhAmSQ6VMERq4xCjh5YNSsjmzdbU5VukKn6iMn1nzoyswB2wsJJVehVwKLspNmg4RpCKLptEtxL", + "slot": 419929899, + "block_time": 1778858822, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764": { + "address": "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 74 + }, + "first_seen": 1768845271, + "birth_edge": { + "sink": "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764", + "sources": [ + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb" + ], + "value": 4000000000, + "signature": "buG8ygWB6h5RLJmX5caSxTXK786wYRXgwfS9RFABnCivqy6yNePkzmn3Hsyr3XVhs28D1qWgbXbThbyZUzymN6p", + "slot": 394584868, + "block_time": 1768845271, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv": { + "address": "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 351 + }, + "first_seen": 1715877601, + "birth_edge": { + "sink": "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 400000000, + "signature": "3vMHgLqfkYX6fLwgJKkv34rnViBSYsD2mwRJx5b2gBnMHjHemR7aMm4e12V7pwYUfDNDTNSctZ132e3GAixT72F8", + "slot": 266156059, + "block_time": 1715877601, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ" + ], + "birth_scan_exhausted": false + }, + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S": { + "address": "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1655749768, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV", + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH" + ], + "birth_scan_exhausted": false + }, + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS": { + "address": "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1782918463, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "birth_scan_exhausted": false + }, + "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2": { + "address": "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 34 + }, + "first_seen": 1760264183, + "birth_edge": { + "sink": "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 21538335, + "signature": "4uNtV2g1sTqRYn7Skm6uidpEgFEL9UUnJhUGECL3rjyahb6vVPFPKQi3ZzV69NuPPPWwwmg7MNQnWQ59XBtqgP7S", + "slot": 372861338, + "block_time": 1760265242, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4": { + "address": "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1710631332, + "birth_edge": { + "sink": "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4", + "sources": [ + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39" + ], + "value": 394037978, + "signature": "3tJT334qtxVLAYkbJ8nGePCKiT4k9nP2DtwPgjwLiQuMbCMcMv6awuiXC19xq9zR9DUM3TAenySiEdiyp2uMygwB", + "slot": 254612541, + "block_time": 1710631332, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39" + ], + "funded": [ + "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg" + ], + "birth_scan_exhausted": false + }, + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j": { + "address": "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "owner": null, + "executable": false, + "signatures": { + "Exact": 499 + }, + "first_seen": 1683797389, + "birth_edge": { + "sink": "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 240000000, + "signature": "2THWQfqsFFiAxK6YpuFSVPJLzqz7mU7jXzvZ79f3aFWPfGCNR2HAhoFGKbhobBrKd4XJhRGd1guerxbyMx3wAn9V", + "slot": 193309880, + "block_time": 1683797389, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [ + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN" + ], + "birth_scan_exhausted": false + }, + "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM": { + "address": "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 270 + }, + "first_seen": 1649945488, + "birth_edge": { + "sink": "3C18CLMqYbfpmjmiKXwLtvLRcNYG7mGLdftHju64YmDM", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 11854550000, + "signature": "5LojLQtWF5mFYcGE1MiSjmeixsnYuPneTWNigfaeMGJ1Yysw88ct5X7eaJdhRTy482fAXSM97EpWV6YhYBeBHQ7n", + "slot": 129710493, + "block_time": 1649945488, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi": { + "address": "3DJFUtrtvrjNWqxKrziAtWQZd1s7283z3Bv5yYyLUZWi", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr": { + "address": "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1785 + }, + "first_seen": 1739523472, + "birth_edge": { + "sink": "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr", + "sources": [ + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" + ], + "value": 1987201900, + "signature": "bzNe8Zg7oE1fXGipW89PL11Q3LbWTdjkKzFGFfSHFnM1LZr93MSbyf5JhPNZ7khZ6twbx9RF41WCFySKZEj14NX", + "slot": 320565860, + "block_time": 1739523472, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ": { + "address": "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 457 + }, + "first_seen": 1638890356, + "birth_edge": { + "sink": "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ", + "sources": [ + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi" + ], + "value": 10000000, + "signature": "3FcspUwHdAXBUQUSD7W2h6rTetqRqEnwBGaviwuYHjBRfb6nP7dpJiGLn1s5CDCQ98neMBr4D8tRAQwCxaQRxtad", + "slot": 110473576, + "block_time": 1638890406, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2J2YzRzZ7YLNva22NVNJBQC4bbRVFoJgJsbxxArUNy88", + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi" + ], + "funded": [ + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ" + ], + "birth_scan_exhausted": false + }, + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw": { + "address": "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1773549082, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr" + ], + "birth_scan_exhausted": false + }, + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J": { + "address": "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1714594823, + "birth_edge": { + "sink": "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J", + "sources": [ + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX" + ], + "value": 500000000, + "signature": "5fTqyzGH9a9g4B626zks91Ln4aRi5iKy5NRGEK9LPy4c1E4XZXnNJK5raAxDDMoADYZDzyEinoYnbnbbS9NNu6w5", + "slot": 263327744, + "block_time": 1714594823, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX" + ], + "funded": [ + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r" + ], + "birth_scan_exhausted": false + }, + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS": { + "address": "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1783195595, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW" + ], + "birth_scan_exhausted": false + }, + "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9": { + "address": "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 254 + }, + "first_seen": 1723048172, + "birth_edge": { + "sink": "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 490000000, + "signature": "4G2fwKebyh9QsNPjGHfPsCiBnjg5DYU99FUojtp5ZEcTnh5wwMxnYK7F11jQmdAsFSKSpxg1MPg67muc2FZroZrg", + "slot": 282148476, + "block_time": 1723048651, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF": { + "address": "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2386 + }, + "first_seen": 1770200497, + "birth_edge": { + "sink": "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF", + "sources": [ + "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU" + ], + "value": 190000000, + "signature": "51n18JMEXfFtcFurC9Eyqe7ZVgmj4qcwLmrfsF6qDdodqteHt7D6vqyidRDdWZ1syv24c84Ri7jGCs6C8nHKokJR", + "slot": 397988088, + "block_time": 1770202555, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR": { + "address": "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 25 + }, + "first_seen": 1782895333, + "birth_edge": { + "sink": "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR", + "sources": [ + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ" + ], + "value": 734553600, + "signature": "36vjnxndxfYcSHBf37rrdBPqcwc2BBpECPGQzSr6RNhbizneiaCKc4xXpiMNNGtk6eqnexRme4xfy3GDLfkPNyRk", + "slot": 430058672, + "block_time": 1782895333, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH": { + "address": "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11742 + }, + "first_seen": 1624516734, + "birth_edge": { + "sink": "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 100000000, + "signature": "fbtWCRyRCWNtSWCCZV2HJXPt9trL74715bnz1R13hWcBnNzSeJDm9Amufv5VFLQUUUXtVLM5nPCuWkteuBx83eW", + "slot": 84190784, + "block_time": 1624516734, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [ + "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf" + ], + "birth_scan_exhausted": false + }, + "4AcmsW4k1JKCRv9irityUMkJ8eMm1JSqG9rR5FcNdZvy": { + "address": "4AcmsW4k1JKCRv9irityUMkJ8eMm1JSqG9rR5FcNdZvy", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1771562805, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW" + ], + "birth_scan_exhausted": false + }, + "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL": { + "address": "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18 + }, + "first_seen": 1758306486, + "birth_edge": { + "sink": "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL", + "sources": [ + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep" + ], + "value": 2100000000, + "signature": "57LKR9sEPC21YHt2pbje7Ur5ACGLqqMUSF5wHYdquAVozDKs3K516nGr5sa47qJdKZiVvQnxTms11orYiYb7uvCP", + "slot": 367913728, + "block_time": 1758306486, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL": { + "address": "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 753 + }, + "first_seen": 1629221283, + "birth_edge": { + "sink": "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 5355472000, + "signature": "2W3H6wz4dVoB44Lnjkh5gnCUF4ZbgxqcmTruSYMG6vgfrP6Su4TeAcdfHTicxnRLqfasnbgZLjitokZRveP23Sp2", + "slot": 92116993, + "block_time": 1629221283, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [ + "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K" + ], + "birth_scan_exhausted": false + }, + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G": { + "address": "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5722 + }, + "first_seen": 1703416057, + "birth_edge": { + "sink": "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 970000000, + "signature": "3EzUczntHsFF95zi2Kt4Sq9Hh8tB932DRH2TfP3RoLXniCmJ9yBBhwdXqQHXv1w9bDHDWoxQQ6qSfeLaxhXqpf5j", + "slot": 237735212, + "block_time": 1703416057, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3" + ], + "birth_scan_exhausted": false + }, + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw": { + "address": "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784500317, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4" + ], + "birth_scan_exhausted": false + }, + "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7": { + "address": "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5237 + }, + "first_seen": 1757495135, + "birth_edge": { + "sink": "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7", + "sources": [ + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm" + ], + "value": 2000000000, + "signature": "2j3tdTF7b9SQxw7tCmbtXBy8tWcoq9oMXAFBbRKVHkvHhu2VF3c9wDgAY3zVNcfHAojEvvkhQLr6oosvPosD8EjV", + "slot": 366585560, + "block_time": 1757781857, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3": { + "address": "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 119 + }, + "first_seen": 1756133000, + "birth_edge": { + "sink": "4aRt9qd2Zffwchw1zyLAPZkeQRgcnPLVpjjaR3JR36v3", + "sources": [ + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G" + ], + "value": 59854218, + "signature": "39DqyYqU7RqGLLLSr8kU3mKL4xQnMVMbFjH5KN4XMoJvUDxBdYKGiwWPBGZWkASf2cYweXxb3wQ6utie6EMAANpu", + "slot": 364966880, + "block_time": 1757134451, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh": { + "address": "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20761 + }, + "first_seen": 1663879176, + "birth_edge": { + "sink": "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 490000000, + "signature": "5kDrBVEWkieCWXHps4mJ9h437RmD7D9Nmyo2uo2UwQsDNUWm6KiAB8T3DNQ8svrsbx11hqdXKcUBNQAY1gACJNwT", + "slot": 151875320, + "block_time": 1663879176, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh": { + "address": "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20 + }, + "first_seen": 1783099754, + "birth_edge": { + "sink": "4r11Baf8NPhvjbAXbDr11UYoXqV8DregFcFiJp47s7fh", + "sources": [ + "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk" + ], + "value": 2238177460, + "signature": "5a7NoCBgFaHpUJqMxVp6A8vBTLQnJ3uWB59UCDYZsTm3SyXmhYfrPkXSrPDJ9jca9LCtWM5eDLK4jUMoybZ4ApV4", + "slot": 430565641, + "block_time": 1783099762, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V": { + "address": "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1756410716, + "birth_edge": { + "sink": "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V", + "sources": [ + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu" + ], + "value": 2039280, + "signature": "qTMcW7d2WNayJ6STKme6zWHcpCvnZm5EKYRc5mQZJqqBMKB8T3pTKBnqrKQczdNSQfz6LZH1ETAJP7Yf9eiR8W7", + "slot": 363139499, + "block_time": 1756410716, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu" + ], + "funded": [ + "2HVwBFmkq1HUfS2RBN3ixqGBL4NhKD5LhyyVX8bHjSu4" + ], + "birth_scan_exhausted": false + }, + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7": { + "address": "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 15 + }, + "first_seen": 1734187345, + "birth_edge": { + "sink": "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7", + "sources": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "value": 139459250, + "signature": "3PEFFsvAZgWyxKcjNoWzSXQJ4XtMGZbsm4BDiXGtRVT2huaSmPbopJf6Psmsg5HJRxeLCo2ewLcvhQC6JkDNTDYj", + "slot": 307446440, + "block_time": 1734188936, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "funded": [ + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6" + ], + "birth_scan_exhausted": false + }, + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV": { + "address": "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV", + "owner": null, + "executable": false, + "signatures": { + "Exact": 55 + }, + "first_seen": 1773235824, + "birth_edge": { + "sink": "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV", + "sources": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "value": 532871511, + "signature": "nxEPT4eY8McAoUhK86oAYzDiTBXmjJgjmbGSdFaKbNZSNiSi9KoJAAP8hAKvQRZGEDExv1r77BapRvx1joJRXHf", + "slot": 405705084, + "block_time": 1773236286, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "funded": [ + "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ" + ], + "birth_scan_exhausted": false + }, + "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB": { + "address": "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 52 + }, + "first_seen": 1770266528, + "birth_edge": { + "sink": "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1204809220, + "signature": "373hVK6YWozQ1rjpFwQ9EYjzYKW7AJ3YEpfyAPGiDtdeMmRphi1bKSpnPn2vu8t8t7J7fKAug14jQu6LdRNUR4Jo", + "slot": 398150397, + "block_time": 1770266528, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ": { + "address": "5E4pqCQBoXQoA3saK1gL4LaPxeGrQn7whwjZ2WTcXkCQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1774679440, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1": { + "address": "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784129811, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb" + ], + "birth_scan_exhausted": false + }, + "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC": { + "address": "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 824 + }, + "first_seen": 1757568229, + "birth_edge": { + "sink": "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC", + "sources": [ + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN" + ], + "value": 800249721, + "signature": "hq8JsmuHcip8MBCGgPSwd7occB8hVnKNphP8Xm4Yk7Uuj9Vtkz5DCQEYMnh5W9bn74jGumD3LC6essjQdzqyWzp", + "slot": 366050029, + "block_time": 1757569412, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD": { + "address": "5VCwKtCXgCJ6kit5FybXjvriW3xELsFDhYrPSqtJNmcD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1741847245, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3HtvC35XD2HEq1x3eNhDARwL7KTfBFePkykTrD4b7HEr" + ], + "birth_scan_exhausted": false + }, + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt": { + "address": "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "owner": null, + "executable": false, + "signatures": { + "Exact": 124 + }, + "first_seen": 1773144352, + "birth_edge": { + "sink": "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 99000000, + "signature": "NbnA7s6aJWNM2nfCACoXNDeVGZwALbRcFCkM33u7mQyMKJ3QpSHhA1pVPVX3EugPo74gm1ycaL1CBJ4UqcN3dXi", + "slot": 405469329, + "block_time": 1773144352, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3" + ], + "birth_scan_exhausted": false + }, + "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z": { + "address": "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 96 + }, + "first_seen": 1628597237, + "birth_edge": { + "sink": "5h14soshoerNL234NwnJjFYHPC4iCF1RwTCBnEQcUd4z", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 4990000000, + "signature": "2kUPLWWUJut6M2AfRAZAiGLkuWLJN5D4JXMZ1VKr7cj7uWWmmcY43cGMHS7UMF8Uw32rrFUdCMNh6AMyJKR7woy9", + "slot": 91034738, + "block_time": 1628597237, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ": { + "address": "5njZuDSS2WTFkvWWsgv8Q4TfQBuCASKJAztc5yF2qLWZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1777762335, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "41vrUt8avJ7ji5X69DHhB8wnf22Z8HMySm4oS8NzhCzR" + ], + "birth_scan_exhausted": false + }, + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9": { + "address": "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784840303, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv", + "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "4ju5foMCMknZiWQrYHjZNnimChCAurmBAejCCJP1C1oh", + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX", + "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "55gcso1fiGyKeqSXAi4Zr3Vmw9RotfF8GLYwouXSevMB", + "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt", + "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "4HUVDexUL67zLvj2Q7AddoNoDvmruYKHuHguQx1Jed7G", + "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "3pQCKrU2S6DDNLmKNASNuTM9BUCfZx2hUycfBHL45Fu9", + "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4" + ], + "birth_scan_exhausted": false + }, + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF": { + "address": "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784797145, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879" + ], + "birth_scan_exhausted": false + }, + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu": { + "address": "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3947 + }, + "first_seen": 1631763613, + "birth_edge": { + "sink": "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu", + "sources": [ + "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk" + ], + "value": 100000000, + "signature": "3CmYn4pR8Eaoa5ocbeLxZ9VHFTtQziQMVNpM2Pjh2V1HiJg3VCnkVskGWtaMVgf4oDRCiEW3RC8r9NbTgjY2aXQ5", + "slot": 96711846, + "block_time": 1631763613, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk" + ], + "funded": [ + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN" + ], + "birth_scan_exhausted": false + }, + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39": { + "address": "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1710624873, + "birth_edge": { + "sink": "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "sources": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "value": 394042978, + "signature": "5reS92mjj8Avu1Hm8NibK41HwVLTaetYhYEb1eGpT3AYkssVwhBEq479YqaVAw7G4tPhrnwvjUai3ruhQLpUW72T", + "slot": 254597447, + "block_time": 1710624873, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE" + ], + "funded": [ + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4" + ], + "birth_scan_exhausted": false + }, + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS": { + "address": "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 194 + }, + "first_seen": 1739874778, + "birth_edge": { + "sink": "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 1000000000, + "signature": "rB8uvcS1mATGDoAm47msudGAkdiXhSJBKGnk51uiWVkyzj45DKjiJUguciDjwujSbLGaxfhYYgC6bBs1bFMwbry", + "slot": 321451943, + "block_time": 1739874778, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [ + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL" + ], + "birth_scan_exhausted": false + }, + "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL": { + "address": "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 66 + }, + "first_seen": 1730986163, + "birth_edge": { + "sink": "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL", + "sources": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "value": 240234545, + "signature": "4JGR17CZKiUrUKXVpnbhu4LzPvRc1vMRChQVcqLpjuWaPswEvUS3b8Cjf5tyNyuptuA5GGBrTt2cCgdv1QsYuwiQ", + "slot": 300009349, + "block_time": 1730986163, + "ambiguous_attribution": false + }, + "fee_payers": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk": { + "address": "6ZRCB7AAqGre6c72PRz3MHLC73VMYvJ8bi9KHf1HFpNk", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1667834292, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu" + ], + "birth_scan_exhausted": false + }, + "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv": { + "address": "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7163 + }, + "first_seen": 1776453682, + "birth_edge": { + "sink": "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv", + "sources": [ + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1" + ], + "value": 11193190, + "signature": "2EFWC7p4UmzuhS8ewbcBi4bJGXe3Nd8tMbRpnLY4oCDG3MTJ1nxhsVLAhzcCHhEvacNdeoyepNE54ow4jY3WnZS5", + "slot": 413882415, + "block_time": 1776453892, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "CRUSHtWNSqWK4nu8zZvXpSKxKveas1LSuG7MycL4T976", + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9": { + "address": "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 41 + }, + "first_seen": 1771265328, + "birth_edge": { + "sink": "6focMqS5RjDZUKcEbPYL2ThVm62e8Ljs3PKshkhANFN9", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1465532000, + "signature": "48jcsENEjMVCHrSfUqXLDZDt8nqQfHqDR1Fhx4VH3wGT5Epbks18Mfcs5WJ3yhzdnuWVupAUKPu6jgFTArDxM7iT", + "slot": 400700540, + "block_time": 1771265328, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV": { + "address": "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 22 + }, + "first_seen": 1781373138, + "birth_edge": { + "sink": "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV", + "sources": [ + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq" + ], + "value": 19650094, + "signature": "5JwERz8A9ssUFHSgXzszdjoKkSs8WVRrjdmKqSRXeD7GcBCyDXqzgMjHQdtqUF9MgCA9R7cK4V7Vm9thS5ib7Fkg", + "slot": 426247804, + "block_time": 1781373138, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG": { + "address": "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1780571139, + "birth_edge": { + "sink": "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG", + "sources": [ + "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW" + ], + "value": 146749060, + "signature": "GhK4Hst1KZyFkxo6viVKoxPJKD3LvVUz66T2joYoM5wacEkn1gbvDtFdNiLB1qXiCMsWiTdGay3xAV3sMHb4o9e", + "slot": 424231157, + "block_time": 1780571140, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU": { + "address": "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU", + "owner": null, + "executable": false, + "signatures": { + "Exact": 369 + }, + "first_seen": 1709827691, + "birth_edge": { + "sink": "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU", + "sources": [ + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN" + ], + "value": 50000000, + "signature": "51rFfVkiSmUzeh1QRNcm99Ea8MUGTMAff1SaRs2dGBigE9VBBo2kkNMsY1o85nxMF4jWHeSVQk2ctJZddtPcZQmj", + "slot": 252697500, + "block_time": 1709827691, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN" + ], + "funded": [ + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY" + ], + "birth_scan_exhausted": false + }, + "71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip": { + "address": "71FdNejCYQFpZ46a9QmAhghw72F6QJdYWRdtKrKQU1ip", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm": { + "address": "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5 + }, + "first_seen": 1784211013, + "birth_edge": { + "sink": "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm", + "sources": [ + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE" + ], + "value": 3895835199, + "signature": "4pntPEhmoeiCTqii4XrvYMiAG2VVja2htxSyFFuda7S8MZfTLjQCkV9XmNyCSqekAzURhwHxAxSEMMApwkM6Zayj", + "slot": 433287475, + "block_time": 1784211013, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T": { + "address": "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 26 + }, + "first_seen": 1783100066, + "birth_edge": { + "sink": "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T", + "sources": [ + "HK2mrz7pr8x9e4J7Bh1rsd6YidBWNk72fR3LUGpdy3hu" + ], + "value": 2247350044, + "signature": "2BdukFLQsAoYPAD8iQpWVwybrX2rZrhp46T5t1yco6c93fAAsY3wyyZdiRHmrBmDMKP1yaVjp4hmjqwa8A6bAoPr", + "slot": 430566389, + "block_time": 1783100066, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ": { + "address": "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2483 + }, + "first_seen": 1642454259, + "birth_edge": { + "sink": "79eU8KVV53emiMcvMhrBEDSLQkrzk2fjgvFGALjaU1AJ", + "sources": [ + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ" + ], + "value": 100000000, + "signature": "4XUZRtA4Q1gAaiSWnDA7XY9RboU57S4bSkhfFscKU8Ywa8NioAKWpCu6Z9ZXRQ18qp2pQAFytHgibJ1So1dsiJ3f", + "slot": 121052633, + "block_time": 1644990871, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ" + ], + "funded": [ + "2cSw92ybJWCZN93RLD2ZCxg3DMXS1v5u3H4BYRExBjbK" + ], + "birth_scan_exhausted": false + }, + "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No": { + "address": "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4949 + }, + "first_seen": 1756672617, + "birth_edge": { + "sink": "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No", + "sources": [ + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh" + ], + "value": 100000000, + "signature": "JTTb13ZHYiJd6AALvqyCnXhSZUFAXLFdKuQmPrXzPkVkYvzL16iDe8zYfBHmTtiLJkCay3pN8DjMdasCr2TqbWf", + "slot": 363900815, + "block_time": 1756711448, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "Hc2vsrdknaVuSypGNyMDuzR3FN3yWVcSuFALT6j2Cq71", + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW": { + "address": "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 41819 + }, + "first_seen": 1764758526, + "birth_edge": { + "sink": "7LXCcQvWfNstriHZzUcJKxSTmUtQWH2KJjbP84ofNBeW", + "sources": [ + "4AcmsW4k1JKCRv9irityUMkJ8eMm1JSqG9rR5FcNdZvy" + ], + "value": 9985000, + "signature": "3fu8JhzdiMdctHfTAjDCULnVrMPbCjEcZkmLvtNUeMUPxmxsq7Nw9UQoFWxPApgKrBJUJtBy5hSrMzCvUsEX5GnR", + "slot": 384183027, + "block_time": 1764758526, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4AcmsW4k1JKCRv9irityUMkJ8eMm1JSqG9rR5FcNdZvy" + ], + "funded": [ + "2cvCppkE6ng6FkgiZigjoFgKsc2t18Jo932HMoTWfg8o" + ], + "birth_scan_exhausted": false + }, + "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg": { + "address": "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 18410 + }, + "first_seen": 1710632250, + "birth_edge": { + "sink": "7Q7NKBZs9cYE6mxrZLYnVBu2rQCbvcks6UawUVeGgRJg", + "sources": [ + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4" + ], + "value": 394012978, + "signature": "3bvxtDsw7CBv3imPLoMrdyhA8cjSRRP3XCwzuJfyzx5AWZ9B8MjPdU3Gju47KgKxqGcd3gLP1ZhLVnHdhaZwQqYE", + "slot": 254614672, + "block_time": 1710632250, + "ambiguous_attribution": false + }, + "fee_payers": [ + "33mh56kAULKie24w43B7pd1jDaXsbebFr296SBhniaq4" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "7g35rQ6bsxgXrMuDqLoYXJFFwjKp6uJkZZujrnLRnTuW": { + "address": "7g35rQ6bsxgXrMuDqLoYXJFFwjKp6uJkZZujrnLRnTuW", + "owner": null, + "executable": false, + "signatures": { + "Exact": 18 + }, + "first_seen": 1628591660, + "birth_edge": { + "sink": "7g35rQ6bsxgXrMuDqLoYXJFFwjKp6uJkZZujrnLRnTuW", + "sources": [ + "BfqUSbXDfxKz99QdzfiiYeuUh5BzqvCVtTppV7DHy4cr" + ], + "value": 10000000, + "signature": "3BKCN6eibAWudB7osE6dbMTVB82nVSBi9QymACMrs1e1EY7a6pTSJ1unJCYf9R7eKqkztukJpvCVTRZ9wyvz6cH7", + "slot": 91024783, + "block_time": 1628591660, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BfqUSbXDfxKz99QdzfiiYeuUh5BzqvCVtTppV7DHy4cr" + ], + "funded": [ + "7tyVqmZ42VWKz9NcktM4drZQrypcnk73F33Brjnmtc88" + ], + "birth_scan_exhausted": false + }, + "7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd": { + "address": "7pfNKw1sABa6Kq3BZCXxD5V81pHQTy4FEr6XtAxaKGKd", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt": { + "address": "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1777777000, + "birth_edge": { + "sink": "7tgzfvXzXq1t1gYW5p8o4wqyK7rHxKEo4AEYPQHDxtdt", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 9000000, + "signature": "48JSB2pqTRMXWg2S6ajewFiuyXADPk9q8QJYD1NLxSYwoBZzSFWKcPEKzQwN2K4dHJ6QYS8ppfo8Acra1LDZkffb", + "slot": 417226530, + "block_time": 1777777000, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "7tyVqmZ42VWKz9NcktM4drZQrypcnk73F33Brjnmtc88": { + "address": "7tyVqmZ42VWKz9NcktM4drZQrypcnk73F33Brjnmtc88", + "owner": null, + "executable": false, + "signatures": { + "Exact": 3 + }, + "first_seen": 1628608093, + "birth_edge": { + "sink": "7tyVqmZ42VWKz9NcktM4drZQrypcnk73F33Brjnmtc88", + "sources": [ + "7g35rQ6bsxgXrMuDqLoYXJFFwjKp6uJkZZujrnLRnTuW" + ], + "value": 24320650, + "signature": "WYyc2zqFMthAQW6w1z79FphC3swbwe5U9WERwpC2xanQhsFomPDqLeoJMU3aemNdDHZYVqEEpywT5sSSaYLm3sz", + "slot": 91055360, + "block_time": 1628608745, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7g35rQ6bsxgXrMuDqLoYXJFFwjKp6uJkZZujrnLRnTuW" + ], + "funded": [ + "HUGi35NLzi1Kch86pkdDJLpkX6dccj5vDjAKwP5SCTy" + ], + "birth_scan_exhausted": false + }, + "83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg": { + "address": "83ZgjvsVN5eBivbjmn8kqmb8upDeqVWPn1v3Jxr3iabg", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4": { + "address": "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 650 + }, + "first_seen": 1764161875, + "birth_edge": { + "sink": "86XYJKnU6fGECepioGn61AtHLxgeSamXDC6WKTJYoiE4", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 100000000, + "signature": "5Djf6M25i7Pqn3PvuibF3UkHSDJAmj3VEiHCuV7wDCth91DGunedSr5EaJ6KwDYQHyqLE6mGTbHM1K7hCg4ejans", + "slot": 382647096, + "block_time": 1764162226, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq": { + "address": "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 92 + }, + "first_seen": 1628599869, + "birth_edge": { + "sink": "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq", + "sources": [ + "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w" + ], + "value": 9880000000, + "signature": "3AVBmBvJmrKM8wMGddX2wk8Z2Jebsk4Tg17Gk1bM1YzMwaVqhqBeLLiU4uhEUeeXGa43AbPVKFCZeYDK3vDWPV5h", + "slot": 91039469, + "block_time": 1628599869, + "ambiguous_attribution": false + }, + "fee_payers": [ + "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w" + ], + "funded": [ + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn" + ], + "birth_scan_exhausted": false + }, + "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65": { + "address": "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1243 + }, + "first_seen": 1760985667, + "birth_edge": { + "sink": "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65", + "sources": [ + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6" + ], + "value": 110000000, + "signature": "3eeFfzc6icmbz7XsoNRMKf1ZtnXKBMUWoXSjY5oarm3GXGzBMJ3QuxuFfMNVHuPtzp8EDsUMh7psK9B85v4QsZ1h", + "slot": 374677619, + "block_time": 1760987283, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6", + "Gem2VAypSg7Ai7vjDKPTtqFahpoQWkfgVkyzx3rPoTka" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z": { + "address": "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 510 + }, + "first_seen": 1736141750, + "birth_edge": { + "sink": "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z", + "sources": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "value": 10450531000, + "signature": "5NTDt6Rswtrvt42AT1trjiRspELnF9t98z5H2TyQ4D1L7zB84w3GQjF6borcJLChNK89mRwQUTsqpowEU6MMp4yQ", + "slot": 312181544, + "block_time": 1736141750, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn": { + "address": "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 928 + }, + "first_seen": 1633089324, + "birth_edge": { + "sink": "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn", + "sources": [ + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq" + ], + "value": 1000000000, + "signature": "qjyV7pemydByaUSd3gd8tGw1uo1JZjM1eg2uKzR8jEs5U6zL1pJRN88EnT6cKsECcGegdcAV5pPbfxkhPJb2B1w", + "slot": 99242855, + "block_time": 1633089324, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq" + ], + "funded": [ + "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i" + ], + "birth_scan_exhausted": false + }, + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS": { + "address": "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1768118748, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs" + ], + "birth_scan_exhausted": false + }, + "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K": { + "address": "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4311 + }, + "first_seen": 1729158047, + "birth_edge": { + "sink": "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "sources": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "value": 200000000, + "signature": "29ZCJmU48wWT7mGpV6u7YsVzcc15Fy6CDPRWRLgjqNYfcgMUU75z79L3ruGa5wXW68AfU9m4EQzr2WtsZ1gmHRgs", + "slot": 299468871, + "block_time": 1730727939, + "ambiguous_attribution": false + }, + "fee_payers": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck": { + "address": "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 37 + }, + "first_seen": 1781689177, + "birth_edge": { + "sink": "9Ey7ic8KJQsuXG16RtQSi4wck5M2Mx7QNXFNjjCVoQck", + "sources": [ + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL" + ], + "value": 2000000000, + "signature": "2kW6Rf9kPJAZnCSgHodJwFNifHr5L1UN3Cj8afcoWHsxHKWFiDEfzFv7v8DsvNL5HFQeihiYy4GWieL826rQnH9Y", + "slot": 427044455, + "block_time": 1781689177, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2UL8hbNaoErAYNePqgQYPP9yDCDRZGWbzKW4krLqkhNL" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr": { + "address": "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1782983366, + "birth_edge": { + "sink": "9FEC1CgLMGE31RVwYLekqrQFtpfgDY1SUaRKaj4X6LQr", + "sources": [ + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw" + ], + "value": 65793663482, + "signature": "4uU5EhqsUfPVzpZ1jHMrg9T2TgfcMWE6s81PwMypZnVXQzjKNiB4ibWb9E2MPHa5WFC5mkZhf2DN4PRtX5zaCnB3", + "slot": 430277120, + "block_time": 1782983366, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3i2HU1oBjZrff9QM6SACYW8C1QjExveDNso9saFSJdYw" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh": { + "address": "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1028 + }, + "first_seen": 1736684311, + "birth_edge": { + "sink": "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh", + "sources": [ + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6" + ], + "value": 30000000, + "signature": "3tBtrKN5a4FNtR2Tti1gYDwxnWoZ9KhNXu1kP4ArQG4q5wDFvU2C2Z3vxS8hgZu2eLkmcT8XR8MfzLHtNfjimhfc", + "slot": 313511470, + "block_time": 1736684311, + "ambiguous_attribution": false + }, + "fee_payers": [ + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6" + ], + "funded": [ + "7KLoH92Gm5nw7JetG3QAKa9RwwHNmoy6uwWtdTZH28No" + ], + "birth_scan_exhausted": false + }, + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E": { + "address": "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 346 + }, + "first_seen": 1619103636, + "birth_edge": { + "sink": "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E", + "sources": [ + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX" + ], + "value": 3999946680, + "signature": "5Xn9C17aBL4Qnk7ZwPJrJeT25CNbP7u7FVGh3qEz3eXKCuqeyM5UNz5KBiW42J3vboohK1ucyZAfeukZybth93Rp", + "slot": 74825696, + "block_time": 1619103636, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX" + ], + "funded": [ + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP" + ], + "birth_scan_exhausted": false + }, + "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb": { + "address": "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 87 + }, + "first_seen": 1761613462, + "birth_edge": { + "sink": "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 5504330, + "signature": "4X7t1Ls8Zpb3bqFZchjukeaG9Tf1MdMwUS62Ua2Nx4ALiEYLR4aSCr2Az9hTVF17HBnJsPUa4Q2amcmFnXWECY9m", + "slot": 376252987, + "block_time": 1761613462, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879": { + "address": "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 59 + }, + "first_seen": 1760849092, + "birth_edge": { + "sink": "9fVYvwTCvHb1pWqiseEpAg9QC32vmvTRhWkHpqNbr879", + "sources": [ + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "value": 93302400, + "signature": "8d5pfjnoehDwiQkD8Uzm2oPh1t4kFwufYmpALykZYwM5KuTQ4dnfNcNHsWS55QDSLYD8mB3YK18epo2sfgEtnY7", + "slot": 376261031, + "block_time": 1761616657, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6LY1JzAFVZsP2a2xKrtU6znQMQ5h4i7tocWdgrkZzkzF" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV": { + "address": "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1764737043, + "birth_edge": { + "sink": "9qjDVFMBzDxg3cS6gMVjR6rFJAubMv51T3NwcrAu5VCV", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 14000853800, + "signature": "4HrqBiVtSmNYMccmWJTEBTXqQe3eztKrd3bpj9AibBehzd7QGCqiY69pbSafREg2UaAHGcv5zt1TU57vbjDFVJdx", + "slot": 416969897, + "block_time": 1777675621, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "7dq4McdQAd6ZUMajZKHNiacNuWcyJsBj1eMeZPPckyz1", + "75Cxo2H9iFZ71LVWADz1qwRV2MJkjzmUdo2b6UjXp1KN", + "6cRBcJy8YatfNYLHD5kyNtCX9ypENwXerB36MKUNUK4e" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR": { + "address": "A77HErqtfN1hLLpvZ9pCtu66FEtM8BveoaKbbMoZ4RiR", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1783931673, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2XuaACNi8Zz73BKPX2QFi7DAqwFWUQo7j39KYP6G86dh" + ], + "birth_scan_exhausted": false + }, + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2": { + "address": "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1781807941, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7", + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "8XnjeyUaXkcUZaAV8ZnVFoEtJgeCyNhVFKuoT5QeTe7z" + ], + "birth_scan_exhausted": false + }, + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE": { + "address": "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 27 + }, + "first_seen": 1726629672, + "birth_edge": { + "sink": "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE", + "sources": [ + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH" + ], + "value": 1000000000, + "signature": "4DvouS9aXw7ir6N3ohxgKFw3UugY7jqK3aextkEtbox1i1wue5Xh4W6NHbZE9bWvZLMb1P28LoMDY4QhBnRXcehr", + "slot": 290465228, + "block_time": 1726629672, + "ambiguous_attribution": false + }, + "fee_payers": [ + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH" + ], + "funded": [ + "71wa8sTfh6RBnsnF4LwWgwnTw2YxLaGFuwsVhRPQqmzm" + ], + "birth_scan_exhausted": false + }, + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP": { + "address": "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 8186 + }, + "first_seen": 1711915637, + "birth_edge": { + "sink": "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP", + "sources": [ + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V" + ], + "value": 200000000, + "signature": "2sbPicuC4gBQYMAM312LbgX1eDfY9nWY1Aeomg7RBbYVq4ZBuxkFJ1Cpc1icLyVxYUvzPmUyBdn5tCjBbBgRco49", + "slot": 257602531, + "block_time": 1711915637, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V" + ], + "funded": [ + "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n" + ], + "birth_scan_exhausted": false + }, + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri": { + "address": "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 10 + }, + "first_seen": 1742326938, + "birth_edge": { + "sink": "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 1003215950, + "signature": "3tm1fffHfNDLhNDnTLeRVe5scKp75BZtrAEcbiDoujfP9be6qpAEwP1heKegTaC1jYcGo8641hi94FUuaj6sZrJF", + "slot": 327633737, + "block_time": 1742326938, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [ + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep" + ], + "birth_scan_exhausted": false + }, + "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU": { + "address": "AMrWYhuEETZDBEwbajGuxHXH3nZHDNk99nYwpb3hDzrU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1754719629, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3pxVaH5o275FjxxK1NDAoUULSSBqTE7GDHkTnBju64MF" + ], + "birth_scan_exhausted": false + }, + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ": { + "address": "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784372764, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z" + ], + "birth_scan_exhausted": false + }, + "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa": { + "address": "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 81 + }, + "first_seen": 1712036070, + "birth_edge": { + "sink": "AcXn5js7WNPTS4VvZ55k4mqZMbE8SyY9mBs8SZgxcdAa", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 270768410, + "signature": "4MjcwLDx2bPhGuHdRFGKiE3khiQoSd5DWZEw7RNbuHTubuJ6MvHtZyG4TA7TTAHc9CeK6yHS68ErF5gZdeKnDC2R", + "slot": 257873921, + "block_time": 1712036070, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX": { + "address": "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 3735 + }, + "first_seen": 1760474300, + "birth_edge": { + "sink": "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX", + "sources": [ + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD" + ], + "value": 100000000, + "signature": "aq7ps7k6RZcBWVMaHcqHLVemRNLYzFQukxseeeApwgmVCZ39LNCa2kh8WLCkkdM21AUUjVv9sYtYmi9Gz9BwkUB", + "slot": 373387624, + "block_time": 1760474300, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4": { + "address": "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 37 + }, + "first_seen": 1775710580, + "birth_edge": { + "sink": "AhsdvQTNjKRKRNoKwX72rU4iMfqUTXDPN5UZjTUQjJy4", + "sources": [ + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw" + ], + "value": 4639648909, + "signature": "3yQYxKyRPrcH6pk87zfhx67eL6Wzza7hDrE91MA3xHKBRcWv6fHLYeW2JfwTtG2sHw5u67qig7NJ2usnX26aqb6t", + "slot": 412000930, + "block_time": 1775710580, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4NyK1AdJBNbgaJ9EsKz3J4rfeHsuYdjkTPg3JaNdLeFw" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8": { + "address": "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 179 + }, + "first_seen": 1739919851, + "birth_edge": { + "sink": "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8", + "sources": [ + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY" + ], + "value": 625744930, + "signature": "3RQ4iKJW9dUmd47Tr4JANamMYoeg33wQPZvLCPB2Xy5SGpB7BjfcwYoV98YCvvbZyjJ8hgrEB3ybUJpTQrBke1oX", + "slot": 321565671, + "block_time": 1739919851, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY" + ], + "funded": [ + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf" + ], + "birth_scan_exhausted": false + }, + "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf": { + "address": "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 450 + }, + "first_seen": 1713195781, + "birth_edge": { + "sink": "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf", + "sources": [ + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ" + ], + "value": 10000000, + "signature": "4CEipGPf7m8m8EfHLDWVTy1TsiKyD1LsD6ZAaaNNgvwWJdvqGxRoo9cPcFbASE47phKqZFzaG9dWKaTCFx99bTSb", + "slot": 260290481, + "block_time": 1713195781, + "ambiguous_attribution": false + }, + "fee_payers": [ + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS": { + "address": "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784479518, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j", + "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD", + "EZA5v8GGTDkPohspz9sVP6gBaP2D7ji3MVLvEsowRcnu", + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL" + ], + "birth_scan_exhausted": false + }, + "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh": { + "address": "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 24077 + }, + "first_seen": 1631688013, + "birth_edge": { + "sink": "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh", + "sources": [ + "F3Jh7vot1xC4gmr4AjcJBKouHEej13KMyySUFk2CFmUc" + ], + "value": 25000000, + "signature": "3vmCJ69AbCNgG1VseFN5Gp3j59XFEYZAXbXE7KjgJP5qa336ZyXUUMvZ8ehs9t3tLgyvKTLM2S56H2uQ6UDuuD24", + "slot": 96579112, + "block_time": 1631688094, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F3Jh7vot1xC4gmr4AjcJBKouHEej13KMyySUFk2CFmUc" + ], + "funded": [ + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL" + ], + "birth_scan_exhausted": false + }, + "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc": { + "address": "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 64 + }, + "first_seen": 1778266848, + "birth_edge": { + "sink": "B1mQeDgFPifbD1soS5PhZG6H2uu3rkDZAZFsGUNuRprc", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 99000000, + "signature": "3UDS6u6ncvTgLndGKD9eaT6q5NWG6JvBniKFJmXeXWPcn9uPsEb5vLWyerKgsPfDvS76r5og2onyCsvXhDw86Kiz", + "slot": 418457793, + "block_time": 1778267806, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i": { + "address": "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 290 + }, + "first_seen": 1757844826, + "birth_edge": { + "sink": "B3wWqwtFZyU1xZM7rWEYan5oJsegs6Z4tkt2fnfBGG7i", + "sources": [ + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn" + ], + "value": 120000000, + "signature": "5aVXbSHPmaWfGTctRcFzMuXnqujTKZAEYTX9G6cByRFswAMPGesDy2vVKWp1e9sykcHkF2QbnhiBQQgbZMhPs7EL", + "slot": 366747475, + "block_time": 1757845725, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "8fzTAsHMTPh8AdfqhiAmzMydVGkYYh1r1bbCntD9XbMn" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn": { + "address": "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784104058, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa" + ], + "birth_scan_exhausted": false + }, + "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM": { + "address": "B6jbBHAZjmD4cxALXRu7rdNNVQKAKtjTSGm496KWa5CM", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 491 + }, + "first_seen": 1757104043, + "birth_edge": null, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "2nh9AXgxVtbDucnR6Psv53JZaWvjphDLMb6HohuKmr5e", + "pay3JiXTbGTdjfoYo1caZ1oGdQgWVLQ3ws8LoqbxU8i" + ], + "funded": [], + "birth_scan_exhausted": true + }, + "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ": { + "address": "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 440 + }, + "first_seen": 1773406077, + "birth_edge": { + "sink": "BRLeB4UANvMEPLqe76uFCyEA3wMTCPLkJqS7KsoYYCbZ", + "sources": [ + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV" + ], + "value": 3164021672, + "signature": "cTRx38ZKrtf7ffFXLkr2E2zVgVeJBugExzbDBrxPH7AnZdU631jwEpEQwgsspZEV8QLGr39u9N9nU3vdSrK7RMi", + "slot": 406138581, + "block_time": 1773406090, + "ambiguous_attribution": false + }, + "fee_payers": [ + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij": { + "address": "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2605 + }, + "first_seen": 1753207964, + "birth_edge": { + "sink": "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij", + "sources": [ + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD" + ], + "value": 140590159, + "signature": "2oaGcywpLPt8xEY7w84WSw4Z94DJiZgz1sTYzpCBbfT8sc5UYD99TiHCZoJqasjmLGzqUbffbHXg7LimmCk5pzu4", + "slot": 355044244, + "block_time": 1753207964, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe": { + "address": "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 320 + }, + "first_seen": 1720840348, + "birth_edge": { + "sink": "BZfQxaepzR4pRLDTGMXSkvqGRh4R48o4TMMnHsXtPeEe", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 11000000000, + "signature": "1rHtsQspU78fmcbJZDfTh4hrN8xcRLjz2Ji2N7VV5KPBW29nATa6FSmt6WsnrFkmnNbz7QmuQiCDEgeRJYieX9E", + "slot": 277240691, + "block_time": 1720840348, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "BfqUSbXDfxKz99QdzfiiYeuUh5BzqvCVtTppV7DHy4cr": { + "address": "BfqUSbXDfxKz99QdzfiiYeuUh5BzqvCVtTppV7DHy4cr", + "owner": null, + "executable": false, + "signatures": { + "Exact": 78 + }, + "first_seen": 1627941588, + "birth_edge": { + "sink": "BfqUSbXDfxKz99QdzfiiYeuUh5BzqvCVtTppV7DHy4cr", + "sources": [ + "GbG7Kekbzjypekv9vEEdyERXp3XvpvAqhtFfvEUwrAb7" + ], + "value": 1924720, + "signature": "3iPiaULqy4K1QhBFMTjHRr5Tk4mkqD5AZQhUUBf26YBmmAfptupMLE9w4FXdx6YcNH5oU6LR2TPu7DDYmcekAx6s", + "slot": 89819719, + "block_time": 1627941726, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GbG7Kekbzjypekv9vEEdyERXp3XvpvAqhtFfvEUwrAb7" + ], + "funded": [ + "7g35rQ6bsxgXrMuDqLoYXJFFwjKp6uJkZZujrnLRnTuW" + ], + "birth_scan_exhausted": false + }, + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU": { + "address": "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784816847, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8" + ], + "birth_scan_exhausted": false + }, + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6": { + "address": "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1783883458, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH" + ], + "birth_scan_exhausted": false + }, + "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb": { + "address": "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 47155 + }, + "first_seen": 1717142675, + "birth_edge": { + "sink": "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb", + "sources": [ + "HFWv1riXSRJ3nVnnLZ9xi1K4r3zppRvcfVuiAGqAA8Y6" + ], + "value": 1495286656, + "signature": "47dN3fYBZ93es4jepSiFL2Ez7j1bR2KCsyWjsEPqifDoZ2K1U2m62tUkp2e1F1BgG6YaUCqAkto4uyUU5Ax95QY4", + "slot": 268999910, + "block_time": 1717142675, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HFWv1riXSRJ3nVnnLZ9xi1K4r3zppRvcfVuiAGqAA8Y6" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg": { + "address": "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2175 + }, + "first_seen": 1656406760, + "birth_edge": { + "sink": "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg", + "sources": [ + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP" + ], + "value": 9995000, + "signature": "uuctf7AvHunnmw7gS6Vn6WdB14iBXvbSCkNj7hacJUYDiFs41nFhzzoP2NqGHfV1HH1pFBEmiMsdRW8tXAmv8Qr", + "slot": 140737056, + "block_time": 1657295936, + "ambiguous_attribution": false + }, + "fee_payers": [ + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP" + ], + "funded": [ + "93PSyNrS7zBhrXaHHfU1ZtfegcKq5SaCYc35ZwPVrK3K", + "6Yx4FmEpbWsyDyMRwdZsMLu47uzZ4CvqXvVSzN64o1BL" + ], + "birth_scan_exhausted": false + }, + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb": { + "address": "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "owner": null, + "executable": false, + "signatures": { + "Exact": 8 + }, + "first_seen": 1768750363, + "birth_edge": { + "sink": "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 30193550020, + "signature": "4sv5REU2obsMfmgbTmSqWgiGiCnQJibid6PgNptnSpqaDLjAWGBdfotB1yiG3tnDuJv7mqRN5NyXQAstjw1QfMdr", + "slot": 394345708, + "block_time": 1768750363, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [ + "2h5b8DXC4obG3Wrvy2tpv2fZ9K9o9zaRBfstUXpY6764" + ], + "birth_scan_exhausted": false + }, + "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm": { + "address": "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 143 + }, + "first_seen": 1767909246, + "birth_edge": { + "sink": "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm", + "sources": [ + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4" + ], + "value": 1310000000, + "signature": "3kDnzFaFpXEegPizm9MKtpGGJ8BnETNFdYFJpd2rxKux7Z8EF58v5sMGsnqHfGAxoPTkiZCcSUUb9VpqWL7LD4og", + "slot": 392216036, + "block_time": 1767909246, + "ambiguous_attribution": false + }, + "fee_payers": [ + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ": { + "address": "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 31687 + }, + "first_seen": 1756413585, + "birth_edge": { + "sink": "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ", + "sources": [ + "EZA5v8GGTDkPohspz9sVP6gBaP2D7ji3MVLvEsowRcnu" + ], + "value": 1000000000, + "signature": "3vd3hUkFUjy3i73ogetQDJmWEQfnL8rtfm9W4SKgbcLmyfpnB2i2CCXBBraQ5MhBzp6JLe73XbQ7JLQGM94t3D5H", + "slot": 363150428, + "block_time": 1756415016, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "EZA5v8GGTDkPohspz9sVP6gBaP2D7ji3MVLvEsowRcnu" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD": { + "address": "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1058 + }, + "first_seen": 1734856327, + "birth_edge": { + "sink": "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD", + "sources": [ + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "value": 491810375, + "signature": "2dMS6hZn46wpPzfXrzPMP6u4qrA8E21Pn5vhdeZGvEtMVxozhJUucnm8XfFiWA7VyrcvZCe8zVQYPdFnGx833BxP", + "slot": 310396534, + "block_time": 1735409017, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HgsxLyn8175xEwRffPRN3DeARE2EVcEeXENr12HpadL6", + "2snHHreXbpJ7UwZxPe37gnUNf7Wx7wv6UKDSR2JckKuS" + ], + "funded": [ + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG" + ], + "birth_scan_exhausted": false + }, + "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG": { + "address": "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 36 + }, + "first_seen": 1778449810, + "birth_edge": { + "sink": "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG", + "sources": [ + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z" + ], + "value": 200000000, + "signature": "4XRjYBMjWrSKs8PTEPYrTgkfwVw8Er4AjCYaiHtVn9L5tRWTGu7HnxBTSKUc6gGYGoLri2jr3U9dJ3HnvDvnz1HE", + "slot": 418909903, + "block_time": 1778449810, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG": { + "address": "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1774751413, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z" + ], + "birth_scan_exhausted": false + }, + "Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK": { + "address": "Cfcm21hnk84Kv7AmEm53jXaCXnHtm7XvCPmBLBcH5rxK", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt": { + "address": "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 17 + }, + "first_seen": 1783210007, + "birth_edge": { + "sink": "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt", + "sources": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "value": 2290497690, + "signature": "3dZV999nzMy8zrpKZkKbhczT6DLA7J9YhLZZZKpFD6U6UoGg9oEUrySMR4VJNGeV6UTJ8rKbq6qLpL3AQ91UWu5S", + "slot": 430838822, + "block_time": 1783210007, + "ambiguous_attribution": false + }, + "fee_payers": [ + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj": { + "address": "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 126 + }, + "first_seen": 1775679727, + "birth_edge": { + "sink": "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj", + "sources": [ + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL" + ], + "value": 346000000000, + "signature": "2qWrYX2VaSpYFeYPpkqG22UArD9XFzXGYmLbcvSD9zNTGSyjB87ZKsAVfq16XzCazqppJMkRB9XG9rMUJBEf6Z29", + "slot": 411923073, + "block_time": 1775679846, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c": { + "address": "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 35 + }, + "first_seen": 1775705585, + "birth_edge": { + "sink": "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c", + "sources": [ + "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ", + "8ncU5YW1CQwvr4gs7buH57bW58e86TDau4STrCJBuz8z", + "FUYyfnVhhRc3PoAyQhWYVGL5oN7BF69R6oSKFjz7jtEh" + ], + "value": 58972050, + "signature": "4Vj6GXww18MxSkppTLZFUPxyjKQQMXgohde7sNGCwZGwgQAiyAr31p73V59467WC1g9iFxPeopMWHAdz3yPdqGsW", + "slot": 411994454, + "block_time": 1775708022, + "ambiguous_attribution": true + }, + "fee_payers": [ + "4PqrAP81NRSgbeBNG3scWVev8RAiy1B5SgRDDo3Ub6Eu", + "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ" + ], + "funded": [ + "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28" + ], + "birth_scan_exhausted": false + }, + "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN": { + "address": "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1565 + }, + "first_seen": 1700506935, + "birth_edge": { + "sink": "CubEiTN5Hi9qwx9Sg4R5BD4aY8sTK2yoxNy3Z7Ve3kzN", + "sources": [ + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j" + ], + "value": 1000000000, + "signature": "3ahr5wC3H6gNPGRX2fZbnSgnSwWo5A7rKcBcpr2MTvpk7LJbALNkrdYuFLunGG3myzFZYMC8ZzPJEtAgzXWbeqrm", + "slot": 231217678, + "block_time": 1700506935, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3Az8dMJrZw64QGKVSq5qmQGQHiLA9eP7hG4e6NwHdN1j" + ], + "funded": [ + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU" + ], + "birth_scan_exhausted": false + }, + "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3": { + "address": "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 92 + }, + "first_seen": 1775659926, + "birth_edge": { + "sink": "D4et37m3txgA4vUVD7r1sW3VU54HxjQEJKeS8nzQbMf3", + "sources": [ + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt" + ], + "value": 57015551, + "signature": "4U4QtmZTpQ15CfqzPENGoU8QKmkR4M3MzAChG1ff7qZQVRFcizNEfrp1JqoaZaAYJSYnvZB9z1fh5hs3rD2Qxa22", + "slot": 411872556, + "block_time": 1775659926, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5deQzA8qG2x6qYUERSzSnosHY5n1xLiDPcxSA3npueLt" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD": { + "address": "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 159 + }, + "first_seen": 1759568115, + "birth_edge": { + "sink": "D5SuN8o8FF9fBT3a7vRNMsbhwbzFNERWpFrE99DN5FAD", + "sources": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "value": 8793000, + "signature": "21n3KFdeWHq3Xb5KkQL5N9gb29bMcU9zgkBmHFK2EnKqpMCpNfhTmMC14oLPRoFMzrcrrUUp34sb1a7ehfKFByE1", + "slot": 371102052, + "block_time": 1759568115, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "funded": [ + "Ah99tgyEJf7GkKNWWprkejhW1UejJwoDDsmR7hJHi8kX" + ], + "birth_scan_exhausted": false + }, + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf": { + "address": "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784517084, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh" + ], + "birth_scan_exhausted": false + }, + "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA": { + "address": "D8cJRpXaCWVK8c3doDq7Ymoz2XE4WyhFhbgNytWwqptA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784730422, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "2WJdW5qeFhvCZawVtd2EZQqacAUAA2NUDfwGE7m269dk" + ], + "birth_scan_exhausted": false + }, + "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep": { + "address": "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 394 + }, + "first_seen": 1746944587, + "birth_edge": { + "sink": "DBCVCfKZDX6UwNnfnVraKDeYjfrLER4dmBtSbj3Lueep", + "sources": [ + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri" + ], + "value": 518078106, + "signature": "4jjjqQ8p9W4WSG7QFKS8FiQszm6XA3gr7ChHQzeeN9nBxWX5DMSErsAunEVF3t1y2Znu2CP95CtjKB8GA4qf8Jz2", + "slot": 339255278, + "block_time": 1746946824, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri" + ], + "funded": [ + "4CpdzBYYVbUDQuSMxvJyvehS6EhcXzYeAytTLmGntvvL" + ], + "birth_scan_exhausted": false + }, + "DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv": { + "address": "DCarCCn8dB94WwE2U2hKXMWT7SUDEXmG7QgQpTQJ2whv", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8": { + "address": "DEZpwAb5wFu3yFcv9Gqu7Fg374gtt9KvNkNkrSwuKKx8", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN": { + "address": "DFF4HX1SVvefEJxCVoAvKkjbeacjXuEopxiqwYxabiQN", + "owner": null, + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8": { + "address": "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 53 + }, + "first_seen": 1781684439, + "birth_edge": { + "sink": "DJ3WNbkGXqS9jJGXofzShz6hMm8FKhBt19pABdXd9TS8", + "sources": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "value": 38618000, + "signature": "5U5bvAmXWokcz4KHXizva51UQDG5PaZyLxQd7fECRdQ8VdX54ZjeW4bf6xBXAvqfLQWJ3394RDzjBbp3UTQX1JiK", + "slot": 427034554, + "block_time": 1781685267, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Biw4eeaiYYYq6xSqEd7GzdwsrrndxA8mqdxfAtG3PTUU" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY": { + "address": "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 527 + }, + "first_seen": 1713772073, + "birth_edge": { + "sink": "DPSzVGbgvC3hK6YJvWzMenwL7vjtoWta97kG5xQMGXCY", + "sources": [ + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU" + ], + "value": 10000000, + "signature": "3HHA4jtCTpR6BeP3G4CfXDsDgX57eVVUQrQZyeosgzrbkhLsjtai66LSgF75UmzWp86gyVWbzLe9PnkJhzzDNCew", + "slot": 261568924, + "block_time": 1713772073, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6rgUKmt7Kvd6GnRQTJRJuGhQr3FydoZKJDXyPBTAXTXU" + ], + "funded": [ + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8" + ], + "birth_scan_exhausted": false + }, + "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z": { + "address": "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 24677 + }, + "first_seen": 1756146669, + "birth_edge": { + "sink": "DRhoK8JXn59s37fumzzPpWwST31TiaE2s7naZ6qmUh1Z", + "sources": [ + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "value": 588787000, + "signature": "4yWtHAoxf24J9RJDTqre7SLD3WPGvtXKBWYhGfyxaqRdmKkfHHpWnAzCcMK1Q6PjXATUdxQ6gthBDghnjcuSSGK", + "slot": 370862324, + "block_time": 1759473301, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "ASTyfSima4LLAdDgoFGkgqoKowG1LZFDr9fAQrg7iaJZ" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP": { + "address": "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 5145 + }, + "first_seen": 1655204850, + "birth_edge": { + "sink": "DVXCjVUkz7yAvwufKgSBSieHjLtkDDc1jWanAZtSt3FP", + "sources": [ + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E" + ], + "value": 100000000, + "signature": "5qCwaVyRDsEr4C3ZndJF99BkYmkRzrGycZXnShE9GujHLnD3CuCc3ETcUXSAop85SrPHBwdjmUdc51yV3GkCYfC3", + "slot": 137511084, + "block_time": 1655204887, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E" + ], + "funded": [ + "C3XueH9USYvEioWKvn3TkApiAf2TjYd7Gpqi83h6cNXg" + ], + "birth_scan_exhausted": false + }, + "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX": { + "address": "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX", + "owner": null, + "executable": false, + "signatures": { + "Exact": 30 + }, + "first_seen": 1714060177, + "birth_edge": { + "sink": "DYz1UqNf2pRoEZygfhN9nUafhjUpcrZQXBqBGccvyPJX", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 987749310, + "signature": "4T9BG6pUpJCYHdubvDe8piyaNYdHW1i8L3Aa3rGokj11eZmiipZBSLJS8updgukiSsjwakJuXsznzjRfcW7hfrhe", + "slot": 262187645, + "block_time": 1714060177, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [ + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J" + ], + "birth_scan_exhausted": false + }, + "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK": { + "address": "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1190 + }, + "first_seen": 1747220417, + "birth_edge": { + "sink": "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK", + "sources": [ + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm" + ], + "value": 3141592653, + "signature": "3mXJiiZA35B1Y6UUyqkynZBkYe8sM4WsjzRBgSfjrvqYRhSRfWSx25Xdr5ct4mFR2UpbTJQg1goGscDGfRa3NdE3", + "slot": 339946664, + "block_time": 1747220417, + "ambiguous_attribution": false + }, + "fee_payers": [ + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi": { + "address": "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 200 + }, + "first_seen": 1653138893, + "birth_edge": { + "sink": "Dbzbxkxpng2NBcUavsveZJpbfXDEbAagPRDrJ4J71fBi", + "sources": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "value": 606489437, + "signature": "62pVXVgr23rpDrywbMT9my8PUPDsRf53biB4QSm9oa67zvw14JmuaqbuC5obnSjSYS1acXMQQRgUXVYpX2s1RHaF", + "slot": 134659485, + "block_time": 1653138893, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2AQdpHJ2JpcEgPiATUXjQxA8QmafFegfQwSLWSprPicm" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa": { + "address": "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7 + }, + "first_seen": 1783201670, + "birth_edge": { + "sink": "DxMTQxafDRzUpb1Xtursz3gndHtHwh6vn7fzr38qCLxa", + "sources": [ + "B48kNVXs4YK4amkBCH2XokQiv1SeiVQGHDR17xDeKAAn" + ], + "value": 185933290, + "signature": "NX3DrMfLgUE4yHC8dgbQj2vHFKiCzTrPhfTDShToZdn8skzKWLJyzEEh6dbWxeMTBEkRhqWkXVRNzmZDvGKs3Yc", + "slot": 430818102, + "block_time": 1783201670, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3ddCq8LgnyYiJFfupiNAR8rQ9BshepSMzSwWbNLageTx" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq": { + "address": "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 17 + }, + "first_seen": 1784199361, + "birth_edge": { + "sink": "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq", + "sources": [ + "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw" + ], + "value": 23682958, + "signature": "2ChU8gFx2ffsnkR6HwHJsD2Pd44iTWQ37rdfWgHwbVjMYb45X88VvqNSCaZu6vh2fgC62bXvkt6C8v9YzUrjNz26", + "slot": 433259174, + "block_time": 1784199361, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9": { + "address": "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 20 + }, + "first_seen": 1782002060, + "birth_edge": { + "sink": "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 50000000, + "signature": "RtgJqd5sHDiondx4Z2yiq5essG1zXj5XwroXbZrtjkhy9NDPjutCezrPBr122sZqphKBa9YWJF3VPxiBUerRjBq", + "slot": 427833377, + "block_time": 1782002060, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U": { + "address": "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 10 + }, + "first_seen": 1781081436, + "birth_edge": { + "sink": "EBNUgPBpAXf14QLeW26LXsqsMowc4Ej73XJNyxP9AQ7U", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 92849110, + "signature": "2d8HpMfLa5r1S3vdLKNiT69B7gPFjj4U3YHRc3skWQFqF3T2LUjTQzjPtWiHzQytw3VupQMQFiajPmjNYzN5rZfe", + "slot": 425512751, + "block_time": 1781081436, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH": { + "address": "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1703354625, + "birth_edge": { + "sink": "ED8vhGPWyBfUi7toDZ6vzuB4TmShsBqaT6Eyr6kC7iLH", + "sources": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "value": 1320200000, + "signature": "3cqmB1Yi2Z7ZVQjjA37NQ4d84ViimuKf1h83J23digAMRGoJMY8VmUbNkSxyqxRqsE3qBoBFKwCMXfxhVMEGNacP", + "slot": 237597989, + "block_time": 1703354625, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BmFdpraQhkiDQE6SnfG5omcA1VwzqfXrwtNYBwWTymy6" + ], + "funded": [ + "ACfTomwEEeikA7FTtBZZ4yh9QoDho7WAj8nfDxQxjHoE" + ], + "birth_scan_exhausted": false + }, + "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm": { + "address": "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 57 + }, + "first_seen": 1756226352, + "birth_edge": { + "sink": "EH2iggdS9PkEop953PN4bzz7CHDZqtDC7mv7iptfHGFm", + "sources": [ + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL" + ], + "value": 19129985905, + "signature": "hwFDdbQ5JJzyNKNESkkX193G8qp1cXkGT1GJfmz6Aca26Kh6Qo54vbWPejmst4DCTFtUW7ifDYZ1XG2UFgru68q", + "slot": 362671528, + "block_time": 1756226352, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2TXA7bkgZ4NA2sj79KVcxCVjGjA7fXMiYM5dBiymeacL" + ], + "funded": [ + "4UcxKrEt2RTRqXhPhGjKt4LBFYKGdKXkesQtosFB2Qn7" + ], + "birth_scan_exhausted": false + }, + "EZA5v8GGTDkPohspz9sVP6gBaP2D7ji3MVLvEsowRcnu": { + "address": "EZA5v8GGTDkPohspz9sVP6gBaP2D7ji3MVLvEsowRcnu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1060 + }, + "first_seen": 1702411020, + "birth_edge": { + "sink": "EZA5v8GGTDkPohspz9sVP6gBaP2D7ji3MVLvEsowRcnu", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 3389000000, + "signature": "47LG2tdxdb2EobJxigyBr9ex7Pq9wUViz9B6jmpciWrdYTTnPM9QV7Wb6K3usRuGkdohVZDd63gu3HmVQFrXFXhL", + "slot": 235528960, + "block_time": 1702411020, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [ + "CEsi1oBKdwwu2uAs2cc7YUFsQJ8x4N9XZrCSW8zSHbiJ" + ], + "birth_scan_exhausted": false + }, + "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW": { + "address": "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW", + "owner": null, + "executable": false, + "signatures": { + "Exact": 2 + }, + "first_seen": 1780571139, + "birth_edge": { + "sink": "EgTLawBEJpUzBR8hVPZNVgr1RLQmR3EfYMTJiWqERgLW", + "sources": [ + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS", + "ATRsNGv2nDw7hSMfkUTBoVUDsFDwN7po7KbecyiGWNB4" + ], + "value": 148788340, + "signature": "5qzt8zfBQNcj1k4sSPUrPrxcBYLMNDTg44S69HPqnkuwYNFXqhc6uJYS8ftreQQPkpNkFjk3nH8bFV5d5M7bWLXh", + "slot": 424231154, + "block_time": 1780571139, + "ambiguous_attribution": true + }, + "fee_payers": [ + "3oUEaNt7uL7pjZ6gdiAiEVRp9ZCcGRec7B5aSvXcjbWS" + ], + "funded": [ + "6kqUt87GfNYvBxpi9WK8RjGyJMSV4mjt8Tr7n3ej3ybG" + ], + "birth_scan_exhausted": false + }, + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi": { + "address": "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 924 + }, + "first_seen": 1624797610, + "birth_edge": { + "sink": "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi", + "sources": [ + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV" + ], + "value": 30170000000, + "signature": "VRM4EXex3RUYf4mnPB4E6jv9D7XmkmHdwbzeXTCTjmCqWpSfq7mKhgRibqwyzFXvu4FjpvYK5NeQrsQ6vbQ6e8A", + "slot": 84652957, + "block_time": 1624797610, + "ambiguous_attribution": false + }, + "fee_payers": [ + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV" + ], + "funded": [ + "3ZZXVgV8vVtkmFD2x31EfwEG3kBBFnFDzeza4RHoR5kZ" + ], + "birth_scan_exhausted": false + }, + "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu": { + "address": "EnxFkSMoJkEzYUkWHubEXYKw2y9HQVUEHGmXrMyQniKu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1774008783, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "4rSid2157LwgYUZPKP8wzDiinhj9xzwrwD4U43bTJg5V" + ], + "birth_scan_exhausted": false + }, + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r": { + "address": "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 6850 + }, + "first_seen": 1714596610, + "birth_edge": { + "sink": "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r", + "sources": [ + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J" + ], + "value": 499994950, + "signature": "32pf2BhJSmb5gunpiH6aruXu1ErmFsqfsuSuXZCrWYfk1cQGrKa1SUC8A2QEeASEkHt14zDoAJs3vJwq1CFrMoNe", + "slot": 263331626, + "block_time": 1714596610, + "ambiguous_attribution": false + }, + "fee_payers": [ + "3mWripBCZNSHXzFYMuJ1FCuFMjERzvNDRTi3MGUrSf1J" + ], + "funded": [ + "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo" + ], + "birth_scan_exhausted": false + }, + "F3Jh7vot1xC4gmr4AjcJBKouHEej13KMyySUFk2CFmUc": { + "address": "F3Jh7vot1xC4gmr4AjcJBKouHEej13KMyySUFk2CFmUc", + "owner": null, + "executable": false, + "signatures": { + "Exact": 64 + }, + "first_seen": 1630955531, + "birth_edge": { + "sink": "F3Jh7vot1xC4gmr4AjcJBKouHEej13KMyySUFk2CFmUc", + "sources": [ + "HUGi35NLzi1Kch86pkdDJLpkX6dccj5vDjAKwP5SCTy" + ], + "value": 26665052, + "signature": "3oB8DhsZ527MLy5RizmCUof2vPHxYDUHM765JutCu76qmC7pTvsAsu7H5Jz9XNMGFoJUkTp6FZbakrqCeU7Ty32e", + "slot": 95279723, + "block_time": 1630955731, + "ambiguous_attribution": false + }, + "fee_payers": [ + "HUGi35NLzi1Kch86pkdDJLpkX6dccj5vDjAKwP5SCTy", + "BVeiZCPoE3CEPBJsej63LsukQwGz2LtNdzuvqXbpoT1m" + ], + "funded": [ + "AposS2QBW5NZ2i3WNbsHVxTJa5CJGfKHJSVfiX3FYQfh" + ], + "birth_scan_exhausted": false + }, + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe": { + "address": "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1785030928, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "2uUTM3h7WqBsoj2UtV7sxwYfCz8wzaD321kv7gy9YKm2" + ], + "birth_scan_exhausted": false + }, + "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo": { + "address": "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13145 + }, + "first_seen": 1719672752, + "birth_edge": { + "sink": "FAShBHsY6cMvoyCTV1oopUBFKPEkftMTwP9SqCFuQCDo", + "sources": [ + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r" + ], + "value": 150000000, + "signature": "5eDgfMWctbmhkp9ZUddUGf5pcyvMr1ReaEK9nXEYipstcuD58uVbr7UVEzkEv2XoVf464y62Wj9f8mfbfkrg3duL", + "slot": 274705160, + "block_time": 1719673479, + "ambiguous_attribution": false + }, + "fee_payers": [ + "EobHT3dMnAxoPa5Wox3ZbE9pQ88dHxqU7FypMnWfWu5r", + "3qaRfWYQitbWQe7CHVvUR4QwQpG7xcaGmtpfeA3LKrXR" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G": { + "address": "FJx61bqznoVfVDFoujYGgJoJsJq4DxDQ8K3YG5kgRm6G", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784659957, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6": { + "address": "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1439 + }, + "first_seen": 1739722292, + "birth_edge": { + "sink": "FKfUBiFJRFquLrwqR4x7dsZdiTVsGoJMvG7v93ajALz6", + "sources": [ + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7" + ], + "value": 4251664084, + "signature": "2PSni2jfVeX9YUufMwGVXWanasSbzHYk7imdYtwDjjSAqERG6T7s4Hx6DWStky2bqrYgpqoqR7xy6KjTbdQ5tC9M", + "slot": 321067692, + "block_time": 1739722292, + "ambiguous_attribution": false + }, + "fee_payers": [ + "4rhzLD41xPiwkcMVddw6jsQxXG6YmyE6cw9MLvPeu8u7" + ], + "funded": [ + "8TRpWVAv9Mm4cuVCyiGMBvQ6icu37HjCoKb3FajYLB65" + ], + "birth_scan_exhausted": false + }, + "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq": { + "address": "FRLHpC2U6fKVbPaN6GG6byW44LCJ9MjJFEy5o4FMo8Zq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1774722184, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf": { + "address": "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 11491 + }, + "first_seen": 1770490600, + "birth_edge": { + "sink": "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf", + "sources": [ + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8" + ], + "value": 11251120, + "signature": "5QwjQCxGUJfMWx2A5LAZoMeXvTKoT2yUU4GgXmo38bpidM3MWsqEouL2qi5Hbnh6ahYraSuUt3dNTAEW6dY9s47C", + "slot": 398740672, + "block_time": 1770498005, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "Ak9XCgo8ks8K1KftUxREdKnD7DHRcqE8ABxY3mB8y4D8" + ], + "funded": [ + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1" + ], + "birth_scan_exhausted": false + }, + "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA": { + "address": "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 120 + }, + "first_seen": 1769087495, + "birth_edge": { + "sink": "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA", + "sources": [ + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" + ], + "value": 430752000, + "signature": "2AFuX4tvKhsFguYnNfiW4QCHsYFeoARyYNbm9hMxqKHpgaX2f6d8qHfioWQsnyDCy5wFHzeUAXoSURUyGDGgmxzj", + "slot": 395193315, + "block_time": 1769087495, + "ambiguous_attribution": false + }, + "fee_payers": [ + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD": { + "address": "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1211 + }, + "first_seen": 1731582585, + "birth_edge": { + "sink": "FqQQ91XsCxJNN2LGMpvJRV3vzDV7wzA4viCz7LSqdiTD", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 146500000, + "signature": "3rHVM8ZmNB7DinaukQx8mKva26Y4YUNNXmxtPwwjZEW5Gpy72tqpWjfRBCW6jZR64WDC5QxLEFiMkAPKNK65MKTY", + "slot": 301468444, + "block_time": 1731640101, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [ + "BTKkA423gFDxXGxuWHGNHGXXtaJrTNUQpzmqDjcemsij" + ], + "birth_scan_exhausted": false + }, + "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p": { + "address": "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 282 + }, + "first_seen": 1707956423, + "birth_edge": { + "sink": "FtLwLfQPHLN1Ya1QfreEFkC6ggWBDX5Yej3VW6Kh4j6p", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 92000000, + "signature": "2Zo3SqZ1zdcF99BygckTotHufbXupGv4FLFyEdVxbSwKedaeJRsp2gb8Uwjgjf8PkwQgsibXyJvpBZwFErrJ6FDc", + "slot": 248216739, + "block_time": 1707956423, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ": { + "address": "Fuz4rVwvFGZFvzU2cTLkXB9tSh4MVBGW59HxY4fdkHNZ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1760949235, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c" + ], + "birth_scan_exhausted": false + }, + "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu": { + "address": "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 13 + }, + "first_seen": 1783033845, + "birth_edge": { + "sink": "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "sources": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "value": 1171377350, + "signature": "5T6rTfTX8JKsNiXf9PCnAEL4w7HLzJfsVBeQwsTRcsuK9TyxUV9ym5MPTpVBepapLkDMQeTPdAEigW14azAfdiT8", + "slot": 430402156, + "block_time": 1783033845, + "ambiguous_attribution": false + }, + "fee_payers": [ + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2": { + "address": "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 2014 + }, + "first_seen": 1728020846, + "birth_edge": { + "sink": "G6JnX6ziFFpXeSH4TdW2A2w3BDUYhvwEAyX1niP1eYx2", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 203950000, + "signature": "5U8Sgir6egiAF84ZZW13rDa1ek69yJpBcTmv227D88J6Gbm69bF6Dq7WKoLSdYEeaeXGPSCmN76jDFXzZEkeQh2Z", + "slot": 293595031, + "block_time": 1728020846, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE": { + "address": "G9X7F4JzLzbSGMCndiBdWNi5YzZZakmtkdwq7xS3Q3FE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784818542, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "FiSYxPca7TfUFFzsuZHaRN2DVbNDWoB1EqijcuZPtGhA" + ], + "birth_scan_exhausted": false + }, + "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1": { + "address": "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 7196 + }, + "first_seen": 1728755528, + "birth_edge": { + "sink": "G9nsGzoDFETzX19tUvgVbit4BYVftngthzgSUJef4fb1", + "sources": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "value": 839200000, + "signature": "2DdKCofnd53ZCijpwVNM4xRHmTXYT8YqeRh99etQB4J3YmX48b1sNn6vq2RGrqtmsgbKq4jH1jHc9DnoSctSurUw", + "slot": 295234119, + "block_time": 1728755528, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AobVSwdW9BbpMdJvTqeCN4hPAmh4rHm7vwLnQ5ATSyrS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG": { + "address": "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4316 + }, + "first_seen": 1743001899, + "birth_edge": { + "sink": "GBrVtJ9WxcsKGJKmvBDkZdqurGzDf293S1JKgqWWmpoG", + "sources": [ + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "value": 200000000, + "signature": "3Rg9fS58hDfbMcnKWU6jeJXUva4g1jetwoNwvp7YiiH1GXDadsF7ZqxutWpxq6VXuHdRWpq61TFtQMs6KRhrkmqr", + "slot": 329323260, + "block_time": 1743001899, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CYar4VLpjCQ14VZAiXeNntta2oVw5CRQz8U1ajhEPYaD" + ], + "funded": [ + "2et5etVQ1ypxTop3KkRrnNr6uyjPmzq8KyCHLaeYEbs7" + ], + "birth_scan_exhausted": false + }, + "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9": { + "address": "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1773 + }, + "first_seen": 1699496374, + "birth_edge": { + "sink": "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 3442545653, + "signature": "Pvpzz5cpDfRekWPtKKux8YPWQuzJhsEonx1YpQLx9HkBiWkknk4Cwj6dyvof1ha8fnmrEkZqGAefpquu6G6HUDD", + "slot": 228889688, + "block_time": 1699496374, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2": { + "address": "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 163 + }, + "first_seen": 1770880341, + "birth_edge": { + "sink": "GGW7fKri4agcKXic3dEuhDRQ3W6DN275HH6NM7cCSju2", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1178878050, + "signature": "3Yn2VJBxAgpBmQ8XD5EXEPEdPfyYuvrRfiJEFCMn4JS3CwYURPu7QTwjsz9Masn96rwjNriemwBLyzFe8ZVzLwbq", + "slot": 399714845, + "block_time": 1770880341, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE": { + "address": "GJRs4FwHtemZ5ZE9x3FNvJ8TMwitKTh21yxdRPqn7npE", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784508412, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "6VkpbHZ4Q5jS42ZeNaGCUUqeQyP4CXrbP5fuDgyVuh39", + "55H6tESQVFVERF6LzGeUev4p1pCDzeVGML2Tk9UeJRmV" + ], + "birth_scan_exhausted": false + }, + "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA": { + "address": "GM8Qz8gmp9N3Rm94q9iTJeHobGBXoCYMhwZYY8zji3LA", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1778466268, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq": { + "address": "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq", + "owner": null, + "executable": false, + "signatures": { + "Exact": 1705 + }, + "first_seen": 1777229947, + "birth_edge": { + "sink": "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq", + "sources": [ + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16" + ], + "value": 3615443530, + "signature": "ev9FqbVk8jCJiy27MypNycKFYYeRJJJCpH6WiNPM5buomXGKnTHqz9YL5heguK76dncGDd8KnEMvKruGMzQFDRc", + "slot": 415842709, + "block_time": 1777229947, + "ambiguous_attribution": false + }, + "fee_payers": [ + "9WL2A89YBr6X47ABKYNzPentWiBA3H8tpaiuf5CaYHx6" + ], + "funded": [ + "6gKd4wGwaB71Yy3BYLUjfV5XzeDWtEuRu5Q24egzEEzV" + ], + "birth_scan_exhausted": false + }, + "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs": { + "address": "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 302 + }, + "first_seen": 1774289101, + "birth_edge": { + "sink": "GRe9XvkvUF2FQ8jpBju4mmccjLrkQutmHTAqCkHTknZs", + "sources": [ + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS" + ], + "value": 1406603230, + "signature": "58WsdGGRCSKky5UXVe4QCaZpALbxjqfksCdHYZUj6HtNo5GmE3SwBi7tfun9dovyXN7CHVXt56c7wc28deCM4NKb", + "slot": 408377546, + "block_time": 1774289101, + "ambiguous_attribution": false + }, + "fee_payers": [ + "8xYmtDVzZN9fGT4kKFQZzbGSJQR5Lu2FPebJ3b1ZKgfS" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL": { + "address": "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 363 + }, + "first_seen": 1763311616, + "birth_edge": { + "sink": "GWgEFWoUBmE9UocKc3DsvDsVyhYXzd1Wc4cmVy5kbkgL", + "sources": [ + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS" + ], + "value": 128226803, + "signature": "5eRiYettfDeQJtbPPvuTnLYDt5fpqjhqxut1xMhVUk3za3oCNTFqNvBAtDwNCWxsWrPrKRQXj5xeECz2VjpFq6zd", + "slot": 380496679, + "block_time": 1763311783, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AF6B9C43LrVEZLSNdoVXuWeXZBM3voyQgykCxRRoUxqG", + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS" + ], + "funded": [ + "ChBw1Zu1j63pzmkAUNUXwgNos56RdYeb7Ls52HuJi6hj" + ], + "birth_scan_exhausted": false + }, + "GbG7Kekbzjypekv9vEEdyERXp3XvpvAqhtFfvEUwrAb7": { + "address": "GbG7Kekbzjypekv9vEEdyERXp3XvpvAqhtFfvEUwrAb7", + "owner": null, + "executable": false, + "signatures": { + "Exact": 12 + }, + "first_seen": 1626721614, + "birth_edge": { + "sink": "GbG7Kekbzjypekv9vEEdyERXp3XvpvAqhtFfvEUwrAb7", + "sources": [ + "Bv4V3C1A2XmzpPdFbqdSmP9q5Rqy9bAHDeBPBXwbG7iz" + ], + "value": 97561, + "signature": "DowUfWFauC8sgYihAc3sk2chPEUyaCiDBRWZnbgex8xTiqbmoyMeJmD5v1umefRT2UQZ2mDRPPXEKUybbjaHPCU", + "slot": 87738989, + "block_time": 1626721614, + "ambiguous_attribution": false + }, + "fee_payers": [ + "Bv4V3C1A2XmzpPdFbqdSmP9q5Rqy9bAHDeBPBXwbG7iz" + ], + "funded": [ + "BfqUSbXDfxKz99QdzfiiYeuUh5BzqvCVtTppV7DHy4cr" + ], + "birth_scan_exhausted": false + }, + "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq": { + "address": "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 19 + }, + "first_seen": 1758453103, + "birth_edge": { + "sink": "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq", + "sources": [ + "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi" + ], + "value": 1111260006, + "signature": "2wqEciaCd8SdPS1BaEWBxZfVTrHk3sMS7PZB8LobFDQA7e49g7awaW44rGj2gRiWbCoU4rW4SWbn7qCpQtPXaetx", + "slot": 368284051, + "block_time": 1758453103, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i": { + "address": "GeeYJ3UaPUjXQsuP7Jet14i1TtLd9tYYyPXY8qnApL5i", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1745978694, + "birth_edge": null, + "fee_payers": [], + "funded": [], + "birth_scan_exhausted": false + }, + "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb": { + "address": "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 253 + }, + "first_seen": 1776516090, + "birth_edge": { + "sink": "GgH9h9rsMuoduVrNeqfettdkPbBcMJpMJSd2KyJ9FBVb", + "sources": [ + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1" + ], + "value": 268000000, + "signature": "3KB7X9tCYoBd3HrsMzGTYQ1avb3ceSiXpHuQv6qQ9LPLKVnxVoUaXdBeKF5qLp71mMTNmHXsNcKe3Hbd4JYs29C8", + "slot": 414039567, + "block_time": 1776516090, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5F1seMKUqSNhv45f6FhB2cFmgJbk8U1avJw7M6TexUq1" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z": { + "address": "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1230 + }, + "first_seen": 1769712791, + "birth_edge": { + "sink": "Gh2FkrqEtKgHmBQKKJ4SMWui9BhjBF6GiinpYCZ2t59z", + "sources": [ + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG" + ], + "value": 159000000, + "signature": "37ERW5HvvBbZ7xPSZK8kHSjpshWttD8PGTqwnw4hrmou8DyqToYBSU1oV1YqHfEdVbqBkkLULdg7VUmuZBsNkVZG", + "slot": 396754483, + "block_time": 1769712791, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CdcVvn23Bzxj14ibBe7xRcK3g8QbrcXKYYQpqkTrW1KG" + ], + "funded": [ + "CZRtotDXeMtvGcUcRUCvSMpXj8grJ7rJUrhRAxi1nQoG" + ], + "birth_scan_exhausted": false + }, + "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf": { + "address": "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 180 + }, + "first_seen": 1745992468, + "birth_edge": { + "sink": "GspnWJp3hkvP9u6tg3DQtJfBM9udWF1QCLnF16tVCVWf", + "sources": [ + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH" + ], + "value": 2000000000, + "signature": "5ZbHXjZUDRS32uKijFGaJzvbiKBMYLHWcuES7gteaUuErcDjefnt9jsanTDhirv5Rs6JdCv9JbvLrNPPY29kLvEv", + "slot": 336839074, + "block_time": 1745993216, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9", + "493qjjgknUqwZevRrgc6FygdfiQ47dg49uAJR41aUvgH" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX": { + "address": "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "owner": null, + "executable": false, + "signatures": { + "Exact": 166 + }, + "first_seen": 1604753907, + "birth_edge": { + "sink": "Gsun7cGFrSUm3N8TEBq7Uu9xz4c9cE4pKdbtETQiSgZX", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 4990000000, + "signature": "5fNviBm3ujhH54CxhFVkMHGhBh3ycHu52naQLLfeipzWZXT5HXfNUswH9ovncyDCLMGWuFTkc8ZfSGwZRZKuFm67", + "slot": 47336493, + "block_time": 1604753907, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [ + "9U3iaCz3Zk9YRdKux1GL78tdc4vEAo4MmFWWs6ZciA1E" + ], + "birth_scan_exhausted": false + }, + "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv": { + "address": "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 49 + }, + "first_seen": 1778543921, + "birth_edge": { + "sink": "GxR4Sbw8xeHrh2pFrnG55ZR9JRGmUyhfoq4TG5ZU9fcv", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 1999000000, + "signature": "2577uZmHHHp14Cdos1o2FWPAe3yv8JQWbbDZ2MXz7UPeYsivzQKF7BxbnhK3x4ACnD4J4HUSi9URYrAfqLY5Et7T", + "slot": 422526013, + "block_time": 1779894352, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe", + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx": { + "address": "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 639 + }, + "first_seen": 1681873953, + "birth_edge": { + "sink": "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx", + "sources": [ + "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK", + "2AdZQmGikAMWahuJRb27PGABQyF6iyQ8aUUYyDDwRRG6" + ], + "value": 1390880, + "signature": "4UjTtnFx1zxwZ3HfMqZJipdoHQ5Gcjujq9gJgoYaraMkeuRfhb3DhbJnFA7aRpYipMV3rWuBNdJG51K4m1gXs5Qj", + "slot": 189363909, + "block_time": 1681936762, + "ambiguous_attribution": true + }, + "fee_payers": [ + "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw": { + "address": "H292B1VbSvD6GuUmSvUvfQstg1Acfzog796uQ7d1ccCw", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "E9wzrbCkKoxiNiYRUskKqS9ob8gfQAtgj8wE8sh1XVGq" + ], + "birth_scan_exhausted": false + }, + "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R": { + "address": "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 9 + }, + "first_seen": 1784990514, + "birth_edge": { + "sink": "H6Y3YXAyFY4bZUmLTYVosLn2d9hYps16HS48HUq3634R", + "sources": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "value": 10091911, + "signature": "8noYJ5aCs9N6rbVBKCGMwVfVeCg1GQHGXhaNHP2rVcBZSMgbgiUpoNtpQ8XkASe78iZs3JqGk3c178jbeFgdrzc", + "slot": 435145890, + "block_time": 1784990514, + "ambiguous_attribution": false + }, + "fee_payers": [ + "F7p3dFrjRTbtRp8FRF6qHLomXbKRBzpvBLjtQcfcgmNe" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS": { + "address": "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784496736, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GEoo2NGFq49Tt2Nx1188Rd7VdPZEB3S1ZoUgwALChCU9", + "6XuEpNrmbv4SsLVgXcQtkvr8NFKg5kS4nrhjX5eM5vYS", + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ" + ], + "birth_scan_exhausted": false + }, + "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi": { + "address": "HBYGbsQxBy3DPMTWT8UYsqTbGQEc2Sq7NnVUnHnS1XAi", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GcfeB9Gf1pBG27uAzePNJtUSExShPkBNtQpzoybZoEq" + ], + "birth_scan_exhausted": false + }, + "HFWv1riXSRJ3nVnnLZ9xi1K4r3zppRvcfVuiAGqAA8Y6": { + "address": "HFWv1riXSRJ3nVnnLZ9xi1K4r3zppRvcfVuiAGqAA8Y6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1771006540, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "C2DNx61rvkUrUSqjb6VbU7wWziQbwxerwTGEksBnGRb" + ], + "birth_scan_exhausted": false + }, + "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K": { + "address": "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 483 + }, + "first_seen": 1756671400, + "birth_edge": { + "sink": "HJuaALQ3srkxJ3bdj3GtaDcme4UXfhnq3nYFtEFPS99K", + "sources": [ + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL" + ], + "value": 100000000, + "signature": "5K8mBvSCxvxNt81ymbqjhH5Umde57URqC9pVdVzczT8JpRrPrKoEo5t8xEqBLWW2N74Fi5vNNKiCVKbBZZvLu8Gq", + "slot": 372063796, + "block_time": 1759948805, + "ambiguous_attribution": false + }, + "fee_payers": [ + "GT2zuHVaZQYZSyQMgJPLzvkmyztfyXg2NJunqFp4p3A4", + "Hc2vsrdknaVuSypGNyMDuzR3FN3yWVcSuFALT6j2Cq71", + "BeVVXuvvGNmFBeK1bazfc2CYdvvJ5AFi4aNv75Ah7vo8", + "4Cq8mMU23qSW47xk9n7jfzooXJFvEUgdaFBnPBfFbVZL" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HK2mrz7pr8x9e4J7Bh1rsd6YidBWNk72fR3LUGpdy3hu": { + "address": "HK2mrz7pr8x9e4J7Bh1rsd6YidBWNk72fR3LUGpdy3hu", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "759Th6hdAEChjN2hQdZ9YyTHx5TWtoxgFkWPzFxQXL6T" + ], + "birth_scan_exhausted": false + }, + "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1": { + "address": "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 653 + }, + "first_seen": 1773754017, + "birth_edge": { + "sink": "HLSTtn8qsn9MkJZhPzCnxTtgE6HN6HcjiSeCQwydTSu1", + "sources": [ + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf" + ], + "value": 128164040, + "signature": "2PwhcVHygfgnrC6dUDdkyZG6XQq8QzBS5PY4ar7MKwUmnCWtXhhVYACkh8goKM7MozqdANUYs4MhnSVagFBJt7kn", + "slot": 407022368, + "block_time": 1773754017, + "ambiguous_attribution": false + }, + "fee_payers": [ + "FSE76cHsLGxJhpaUb9hVCeT6i8AqbWkGrqYQx9LN9fcf" + ], + "funded": [ + "6bz1PfVhJfZmjBUEx6xkMQL5De7TMfSVqTEXdQRj4Rtv" + ], + "birth_scan_exhausted": false + }, + "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw": { + "address": "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 4 + }, + "first_seen": 1783107306, + "birth_edge": { + "sink": "HM1mCH39pK2UB6jZHoBWoVQg1cqoYM11S1AuYvcD98Qw", + "sources": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "value": 9000000, + "signature": "4B1byRx7pvEAt5NfXDUwEqo2TyMbm1qFV1Qakd6tnjmjfUHQ6ThQJFpcN129xXoS29GPPtvScVyHGBkCV6kd9s2m", + "slot": 430584264, + "block_time": 1783107306, + "ambiguous_attribution": false + }, + "fee_payers": [ + "5tzFkiKscXHK5ZXCGbXZxdw7gTjjD1mBwuoFbhUvuAi9" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh": { + "address": "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 322 + }, + "first_seen": 1761374606, + "birth_edge": { + "sink": "HMXoJu4fRNkBBy4HS8DBHonJFrVZNacpKAaogWqjYoEh", + "sources": [ + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" + ], + "value": 25732740, + "signature": "3AhoNZZLvSQ5gNfWsTeRkjLi8W9t1P63EPXD3YKxErjnAP37U44njwCmpBaygX1xi5fgUNHHSZaeMiEJq9F2jrHi", + "slot": 375651141, + "block_time": 1761374606, + "ambiguous_attribution": false + }, + "fee_payers": [ + "D89hHJT5Aqyx1trP6EnGY9jJUB3whgnq3aUvvCqedvzf" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "HUGi35NLzi1Kch86pkdDJLpkX6dccj5vDjAKwP5SCTy": { + "address": "HUGi35NLzi1Kch86pkdDJLpkX6dccj5vDjAKwP5SCTy", + "owner": null, + "executable": false, + "signatures": { + "Exact": 45 + }, + "first_seen": 1628144077, + "birth_edge": { + "sink": "HUGi35NLzi1Kch86pkdDJLpkX6dccj5vDjAKwP5SCTy", + "sources": [ + "7tyVqmZ42VWKz9NcktM4drZQrypcnk73F33Brjnmtc88" + ], + "value": 24315650, + "signature": "3erwrev4k27SHrAaug5ixMEMbB4bt1dRMmET5Z8YRa8eN8qpsiKdZjJ358UJxD2L56tmsvrCoHNaWUYYzKjtnn6N", + "slot": 92971861, + "block_time": 1629719306, + "ambiguous_attribution": false + }, + "fee_payers": [ + "BVeiZCPoE3CEPBJsej63LsukQwGz2LtNdzuvqXbpoT1m", + "7tyVqmZ42VWKz9NcktM4drZQrypcnk73F33Brjnmtc88" + ], + "funded": [ + "F3Jh7vot1xC4gmr4AjcJBKouHEej13KMyySUFk2CFmUc" + ], + "birth_scan_exhausted": false + }, + "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16": { + "address": "HWjmoUNYckccg9Qrwi43JTzBcGcM1nbdAtATf9GXmz16", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1781299438, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "GPa8qRLhtXojtAR5SG9AcZ2WH32N8V4KDXWip9Waq3Hq" + ], + "birth_scan_exhausted": false + }, + "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V": { + "address": "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 1582 + }, + "first_seen": 1700759478, + "birth_edge": { + "sink": "Hkhy3fzLpxhtSZ1UWmkRtSCzmtYYsDdz8dZBPS9iWC7V", + "sources": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "value": 951634000, + "signature": "5vBY9n8k1qYPBcapzouiPJ9whnVFx18Y9Y574PJfDDwMNKt59QoCsGbRgke24y6T3zTyjNNRqEzCnJUAwfWGNeE7", + "slot": 231795042, + "block_time": 1700759478, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AC5RDfQFmDS1deWZos921JfqscXdByf8BKHs5ACWjtW2" + ], + "funded": [ + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP" + ], + "birth_scan_exhausted": false + }, + "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n": { + "address": "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 135 + }, + "first_seen": 1780765625, + "birth_edge": { + "sink": "JWe9oumM2S71bw3371v3gfKY2pQtNis6nGiFp2Wgi8n", + "sources": [ + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP" + ], + "value": 271700000000, + "signature": "5R2ekhoYBR8fWYnM27srHHH24YNpF79Bu2uws2dh3otMMSt7Kku9tgV5NUx2yFN53YjfhZ8iDv6zNzFJpuoZgyg2", + "slot": 424718918, + "block_time": 1780765625, + "ambiguous_attribution": false + }, + "fee_payers": [ + "AEGLU3WiZcuc6yDx1saV4ay1CVFSPTXiExRVRr5iDngP" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6": { + "address": "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 337 + }, + "first_seen": 1719988520, + "birth_edge": { + "sink": "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6", + "sources": [ + "RBNV3esFPQ4jAkzW9hzxtZj34vJ9Snzrm6arXLs1mYZ" + ], + "value": 669445618, + "signature": "2xwdfPkgWLvHhKL2f1su7F6qyRR7LgaNsYiakS8wpTpeoMc89PLbARf8HR29phRhtdRyRbkj6iWbfbB6KVa68pZX", + "slot": 275394508, + "block_time": 1719988520, + "ambiguous_attribution": false + }, + "fee_payers": [ + "7FfB2zQRYUQwpPzkRxAeg2mCBGeCRKp4PCEeULJA9xTo" + ], + "funded": [ + "9FcCfYD4nriwojppNTPRjYyvw647bMH2tbYwjuGN1Zdh" + ], + "birth_scan_exhausted": false + }, + "RBNV3esFPQ4jAkzW9hzxtZj34vJ9Snzrm6arXLs1mYZ": { + "address": "RBNV3esFPQ4jAkzW9hzxtZj34vJ9Snzrm6arXLs1mYZ", + "owner": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA", + "executable": false, + "signatures": { + "Exact": 0 + }, + "first_seen": null, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "PK6ZHjXu2r4FwcPBtCFJCKs6gbea1U2JNvBpNXpVLq6" + ], + "birth_scan_exhausted": false + }, + "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN": { + "address": "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 104 + }, + "first_seen": 1757553631, + "birth_edge": { + "sink": "ffHGfMu2CpRtowXyZU6uGsj21gr8PZBpLpQBP6JF3yN", + "sources": [ + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu" + ], + "value": 823856392, + "signature": "43aFuJ62SGgRmkXfrQfgydDDuYgrbTzLoNbZ4LD3afHdZyjd9SnUv9o64bxryezdAGxHpBVUt3Eyj8eSeYKtiCiS", + "slot": 366017478, + "block_time": 1757556380, + "ambiguous_attribution": false + }, + "fee_payers": [ + "6PZZAFrg1tnfGg5VhFdqqE6qDtbP7ChyYAsQ2iMax1Cu" + ], + "funded": [ + "5L2WqG4H2x1mcpL9F39YAAZbTC1WdA8HjJA9sBBbm4YC" + ], + "birth_scan_exhausted": false + }, + "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu": { + "address": "iGdFcQoyR2MwbXMHQskhmNsqddZ6rinsipHc4TNSdwu", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784395426, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CCPurEuY4xLWaWf6GynxdnuNDCD7wQCGTYTFwvNUkvcb", + "CfvWj5Zd6k4Tm864g61jy9QqMfSa5D7ZiCuEbbWoAcxt" + ], + "birth_scan_exhausted": false + }, + "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV": { + "address": "is6MTRHEgyFLNTfYcuV4QBWLjrZBfmhVNYR6ccgr8KV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1784374285, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "9Y8V742EZCvKGJvAnmJFckn3QAaZNXhnYk2MdLGSzTdb", + "EADLiUN4qnDgsQdQyQn8A6hV1o8LtuWX6ZDR92Vuv5B9", + "G2uhJ73CLDt2DnodxEdR81HsHcZ53YonsHcL6J5DZxhu", + "AKW1MakJXmUa8Ux5pCRai8zKVAexhhG8d8mZCoQKX4Ri" + ], + "birth_scan_exhausted": false + }, + "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4": { + "address": "jmLtKSEWyQhCbHgTa52XZwcfLQ2ze9J8VN3nfSTEjt4", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1772684886, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "CEsP4bJmNnTcbe9Pyxm8nuAy3Xq8uWZA8L7zbNqiAEcm" + ], + "birth_scan_exhausted": false + }, + "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK": { + "address": "mgrArTL62g582wWV6iM4fwU1LKnbUikDN6akKJ76pzK", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1712393128, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "H28TRhAFrwYcjfEgdJWcVWybT1tg614SSn2M8rd2bsMx" + ], + "birth_scan_exhausted": false + }, + "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ": { + "address": "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ", + "owner": null, + "executable": false, + "signatures": { + "Exact": 21 + }, + "first_seen": 1709918366, + "birth_edge": { + "sink": "og4JD8pHjchseQfyMK56upFjAzNirKZM8unbpPQ1YEJ", + "sources": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "value": 1696237745, + "signature": "qdFZbtFQnSrYx5CCo78yqoFY1ZYvhKgUL5gPeKiiG1LJ2TG2jC4yKtUdwidZiH5tZZ2bt4HEqU4pfjBbMeDYJNc", + "slot": 252918864, + "block_time": 1709918366, + "ambiguous_attribution": false + }, + "fee_payers": [ + "H8sMJSCQxfKiFTCfDR3DUMLPwcRbM61LGFJ8N4dK3WjS" + ], + "funded": [ + "AkbDjkVX9zXWUzRpfbD8pZh7ZHcyZEDSTV3FDwyZgqxf" + ], + "birth_scan_exhausted": false + }, + "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28": { + "address": "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 98 + }, + "first_seen": 1780986875, + "birth_edge": { + "sink": "okXSZcokQCJXD2B6RywVR9KaQS2NeuUWVfCYuwmBq28", + "sources": [ + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c" + ], + "value": 30000000, + "signature": "5RkppwfwvNhhTswurZaM4gwPQiDaie82UhBdHt8mHJSES3ci8cmDuHHRLYXoFuWD7KdurykQXq7dUBfaBw8HAiqW", + "slot": 425274321, + "block_time": 1780986875, + "ambiguous_attribution": false + }, + "fee_payers": [ + "CrnsXJJGBEM4d6eYwKtZ7xccdnWRTv7qUAfu3eo7Aj8c" + ], + "funded": [], + "birth_scan_exhausted": false + }, + "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm": { + "address": "opLSF7LdfyWNBby5o6FT8UFsr2A4UGKteECgtLSYrSm", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1740542110, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "Daq4W5wUTNw7sRXxFvLruk13wjpedVcgBB4rjTzx3DhK" + ], + "birth_scan_exhausted": false + }, + "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV": { + "address": "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 12 + }, + "first_seen": 1619359279, + "birth_edge": { + "sink": "sCi7J7vm9ZVZjP6hpALTjwfu8q8r8WAyDRH1tigdSqV", + "sources": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "value": 31483630200, + "signature": "f6cQSbVaFKTPqNogr3WADjYPSr8ZgwcvYK5uaJiAP9A63W7WZgCjcDoGHxZWK8fdAVBqJzsjF43XYsuCG42fsNX", + "slot": 75325833, + "block_time": 1619359279, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2ojv9BAiHUrvsm9gxDe7fJSzbNZSJcxZvf8dqmWGHG8S" + ], + "funded": [ + "Eh4xhABcg3985rP8Pyv6yTv6vogugh4Hv2cQJRbaDiMi" + ], + "birth_scan_exhausted": false + }, + "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w": { + "address": "u6PJ8DtQuPFnfmwHbGFULQ4u4EgjDiyYKjVEsynXq2w", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "AtLeast": 48000 + }, + "first_seen": 1783682136, + "birth_edge": null, + "fee_payers": [], + "funded": [ + "8DtiG89DwrvkwctcC2U3vGwjhEkngfi1jHh2pHNUGxMq" + ], + "birth_scan_exhausted": false + }, + "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ": { + "address": "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "owner": "11111111111111111111111111111111", + "executable": false, + "signatures": { + "Exact": 34 + }, + "first_seen": 1784808222, + "birth_edge": { + "sink": "umnovSeCExJ3ec1gypSne9cWQjetj45GnWuButiWPRQ", + "sources": [ + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv" + ], + "value": 100000000, + "signature": "3XFtWHvCubsszFNB4U9XX592rAbYqdKaUjy7KPTjxKmcAUziERiSHsPLvbSozHGgAgiZjZmdStGTB99ASeYB8GPU", + "slot": 434711620, + "block_time": 1784808222, + "ambiguous_attribution": false + }, + "fee_payers": [ + "2mrhMbUXyffTymehpWDr59YzXVi6h7v6otbJjLGfzhCv" + ], + "funded": [], + "birth_scan_exhausted": false + } + } +} \ No newline at end of file diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index d25402f3..1752fd01 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -515,6 +515,69 @@ regardless of the resolution rate, because that would mean the members this run added are not a fair draw and the extra resolution bought nothing but a better-looking denominator. +### Run 8 — result: the prediction was wrong and the comparison stays unachieved + +Helius, archival probes passed. 3,133 calls, 699 seconds. + +``` +attempted 92 | resolved 42 | evidence-unresolved 0 | budget-unresolved 50 + | scope-unresolved 0 | rpc failures 0 (0.00%) +``` + +| | Run 7 | Run 8 | +|---|---|---| +| signature page cap | 24 | 48 | +| rpc calls | 1,887 | 3,133 | +| resolved | 38 | **42** | +| needed for the gate | 46 | 46 | +| ρ (point) | 0.0362 | 0.0335 | +| classes observed | 30 | 33 | + +**The prediction failed.** It said resolution would rise above the 46 needed. It +rose to 42. Doubling the page cap bought **four members for 1,246 calls** — about +311 calls each, against an average of 50 in Run 7. The prediction that ρ would +fall was right, and the one that mattered was not. + +**So the comparison is refused again, and this time it is final.** + +``` +privacy pool 54 ρ 0.0955 resampled 0.0848 .. 0.1790 bias +0.0276 +staking control 42 ρ 0.0335 resampled 0.0428 .. 0.0657 bias +0.0188 + +difference: +0.0619 95% +0.0307 .. +0.1267 +``` + +The difference still excludes zero and still points our way. The gate still holds +it back. Run 8's stopping rule was absolute and is honoured: there is no Run 9. + +**The selection check declined to run at the new margin**, and correctly: only 4 +members were newly resolved, below the 5 it requires, so it reported nothing +rather than computing a number from four points. The check at the earlier margin +stands and is the evidence on record. + +### What the marginal cost says, which is the actual finding here + +The interesting number in Run 8 is not `ρ`. It is **311 calls per additional +resolved member, against 50 for the population as a whole** — a sixfold +deterioration from doubling the budget once. + +That reframes the refusal. The control is not under-resolved because we were +stingy; it is under-resolved because the members still missing have genuinely +long funding chains, and each further one costs more than the last. Reaching 46 +would plausibly cost as much again as the entire run, and reaching a comfortable +margin above it, several times that. + +**A depositor population that resists provenance tracing this hard is itself a +result**, and it is the opposite of what one would guess: the *staking* pool — +where nobody is seeking deniability — is markedly harder to trace than the +privacy pool, whose depositors resolved at 65% for a third of the cost per member. + +We do not claim to know why. Two candidates we cannot separate with this data: +staking attracts older wallets with deeper histories, or privacy-pool depositors +disproportionately fund from exchanges, which terminate in one hop. The second +would, if true, sharpen the concentration finding rather than soften it — which +is precisely why we are not asserting it. + ## What we do not conclude **Nothing about how private that pool is, and the headline does not become that From 5975a3ef625249c765b663c26a91a5b9b01519b5 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 03:38:21 -0300 Subject: [PATCH 51/83] docs: correct three claims a reader could have falsified in one command The README said 183 tests; there are 193. It listed seven CLI commands; there are nine. And it said the two runs that produced no headline have no committed sample -- but Run 1's sample is committed, as data/sample-smoke.json, and analyze on it reproduces the 12-attempted, 0-resolved census that makes it worth keeping. Only Run 2 is uncommitted. Replaces the vague sentence with the actual table of six samples and which run each belongs to, plus the three commands that reproduce every published figure from them offline. The same table now heads the measurement log, because a reader checking a run should not have to guess which file it came from. Also corrects the headline paragraph, which still described the Run 3-to-4 coverage finding as though it were the current sample's. It is history now: coverage on the headline sample is 0.65, Chao1 108 against 23 observed. --- README.md | 51 ++++++++++++++++++++++++++--------------- docs/MEASUREMENT_LOG.md | 22 ++++++++++++++++++ 2 files changed, 54 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index cbb0ad7c..b74267eb 100644 --- a/README.md +++ b/README.md @@ -48,13 +48,16 @@ So this submission claims exactly two things: member's funding class costs that pool roughly an order of magnitude of its nominal anonymity. - The sample's class distribution is heavy-tailed and two-thirds unobserved, - and that is reported as a finding rather than hidden as a caveat: raising the - traversal budget between runs resolved ten more members and made coverage - *worse*, because the new members landed in new singleton classes rather than - in the observed ones. There is no budget at which this distribution becomes - well-observed. `docs/MEASUREMENT_LOG.md` has every run, including the two - that produced nothing, and Run 4's budget was committed before it ran. + The sample's class distribution is heavy-tailed and most of it was never + observed — Good–Turing coverage 0.65, with Chao1 estimating 108 classes + against 23 seen — and that is reported as a finding rather than buried as a + caveat. One earlier run resolved ten *more* members and came back with + coverage *worse*, because the new members landed in fresh singleton classes + rather than in the observed ones. There is no budget at which this + distribution becomes well-observed. + + `docs/MEASUREMENT_LOG.md` has all eight runs, including the three that + produced no headline and the one whose pre-registered prediction was wrong. Anything we cannot support with a measurement whose method is published, we do not say. There is a section below of things we deliberately do not claim. @@ -67,9 +70,9 @@ not say. There is a section below of things we deliberately do not claim. | `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain. | | `crates/mirror-circuit` | R1CS gadget, membership circuit, prover, key export. | | `crates/mirror-provenance` | The funding-provenance measurement. | -| `crates/mirror-cli` | `setup`, `verify-setup`, `check-endpoint`, `seeds`, `collect`, `analyze`, `soak`. | +| `crates/mirror-cli` | `setup`, `verify-setup`, `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection`, `soak`. | -**183 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**193 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -180,16 +183,26 @@ mirror selection --earlier lo.json --later hi.json # are the unresolved missing ``` `data/sample-privacycash-run6.json` is the committed artifact behind the -headline. Every earlier sample stays committed beside it — Run 3, Run 4, and the -control — so a reader can recompute not only the number but the corrections: -the budget increase between Runs 3 and 4, and the tracer fix between Runs 4 and -6. None of it needs RPC access or trust in how our endpoint behaved. - -`docs/MEASUREMENT_LOG.md` records every run, including the two that produced no -headline at all: one whose frame was size-biased and resolved nothing, and one -that came back above the 1% RPC-failure limit. Those two have no committed -sample, and saying so is part of the record — what is committed is what the -published numbers come from. +headline. Six samples are committed in all, and they are what make each +correction checkable rather than merely described: + +| file | run | what it is | +|---|---|---| +| `sample-smoke.json` | 1 | the size-biased frame. 12 attempted, **0 resolved** | +| `sample-privacycash.json` | 3 | member-weighted, smaller budget | +| `sample-privacycash-run4.json` | 4 | bigger budget, broken tracer | +| `sample-privacycash-run6.json` | **6** | **the headline**, tracer fixed | +| `sample-marinade.json` | 5 | the control, broken tracer | +| `sample-marinade-run7.json` | 7 | the control, tracer fixed | +| `sample-marinade-run8.json` | 8 | the control at double the page cap | + +`mirror analyze` on any of them reproduces that run's numbers offline, and +`mirror selection` on a pair reproduces the missing-at-random check. Run 1's +sample is committed too, and it is the one that resolved nothing — a failed run +is evidence about the method and is kept as such. + +**Run 2 is the one exception**: it came back above the 1% RPC-failure limit and +its sample is not committed. Saying so is part of the record. **Every run since Run 4 had its budget and its prediction committed to git before the collection started**, so the parameters are declarations rather than diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index 1752fd01..57c4241a 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -3,6 +3,28 @@ Every collection run, including the ones that produced nothing. A measurement project that only records its successful runs is not reporting, it is selecting. +## The artifacts, so every run below is checkable offline + +| run | sample | frame | +|---|---|---| +| 1 | `data/sample-smoke.json` | `data/seeds-smoke.txt` | +| 2 | *not committed* — above the RPC-failure limit | `data/seeds-privacycash.txt` | +| 3 | `data/sample-privacycash.json` | `data/seeds-privacycash.txt` | +| 4 | `data/sample-privacycash-run4.json` | `data/seeds-privacycash.txt` | +| **6** | **`data/sample-privacycash-run6.json`** — the headline | `data/seeds-privacycash.txt` | +| 5 | `data/sample-marinade.json` | `data/seeds-marinade.txt` | +| 7 | `data/sample-marinade-run7.json` | `data/seeds-marinade.txt` | +| 8 | `data/sample-marinade-run8.json` | `data/seeds-marinade.txt` | + +``` +mirror analyze --sample data/sample-privacycash-run6.json +mirror compare --sample data/sample-privacycash-run6.json --against data/sample-marinade-run8.json +mirror selection --earlier data/sample-marinade.json --later data/sample-marinade-run7.json +``` + +All three are pure and offline. Nothing below needs our endpoint, our key, or +our word for how a run behaved. + --- ## Run 1 — 2026-07-25, pipeline validation, no publishable result From c522e91b561dfafa26e1ef47a4e046bc5c593080 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 03:41:11 -0300 Subject: [PATCH 52/83] docs: the three uncertainties and how each is kept apart, in ARCHITECTURE --- docs/ARCHITECTURE.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 9446a2c5..aca5359c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -236,6 +236,35 @@ is independent of `k` and therefore comparable across pools. Effective-k measure at small `k` systematically understates the steady-state loss and cannot be extrapolated upward. +### Three uncertainties, kept apart + +They answer different questions, and collapsing any two of them into one number +is how a provenance figure comes to mean less than it appears to. + +| | question | mechanism | +|---|---|---| +| unresolved bracket | what if the unresolved had landed differently? | exact bound over both extremes | +| sampling spread | how much of `ρ` is *which* members we drew? | bootstrap over members, seeded and published | +| selection | are the resolved members a fair draw of the classes? | one frame at two budgets, cheap vs expensive to trace | + +The third is the one usually left as an assumption. `mirror selection` tests it, +and **separation is the bad outcome**: it would mean the unresolved are not +missing at random, that the resolved subset is biased toward whatever is cheap to +trace, and that no extra budget repairs it. Measured on both populations here, it +does not separate. + +`mirror compare` then bootstraps the *difference* between two populations rather +than subtracting point estimates, and refuses to rank them when the interval +contains zero or when either side resolves under half its members. It has refused +on both grounds — on the first for one pool measured at two budgets, which ought +not to separate, and on the second for the cross-population comparison this +project most wanted. + +One bias is not fixed and runs against us: plug-in entropy is biased low at small +`n`, so `ρ` is biased **high** and the pools plausibly leak less than reported. +It falls the same way on every population measured the same way, which is what +keeps a difference meaningful where an absolute number is shaky. + `2^H(C)` — entropy over the class-size distribution — is widely quoted as the effective anonymity set and is **inverted**: it is maximised when every member stands alone, which is total deanonymisation. It is the leakage. The anonymity is From 6ed96d2e8ab214b7e31ab94772b915d0fe382cee Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 16:35:52 -0300 Subject: [PATCH 53/83] docs: drop the comparative claims a reader cannot check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Six places still argued by comparison — "no prior submission performs it", "a rival's published interval", "the one problem every serious submission concedes". Two problems with each. They are unverifiable: a reader cannot audit work they do not have, so the sentence asks for trust at exactly the point the rest of this repository refuses to. And they were load-bearing for nothing — every one sat next to a technical reason that stands alone. So each keeps its substance and loses its comparison. R1 is justified by what skipping it costs (an exchange hot wallet and an AMM vault collapse into one class) rather than by who omits it. The confidence-interval failure is stated as the shape of the bug — a width that is a function of the resample count, not of data — which is the part worth guarding against, and the reason the guard is a test here. Prior art keeps its names: Tornado Cash, Wang et al., Elusiv, Light Protocol. Those are published and checkable, which is the difference. Also fixes a README inconsistency: three runs produced no headline, not one, which the same file says correctly ten lines earlier. --- README.md | 6 +++--- crates/mirror-provenance/src/classify.rs | 6 ++++-- docs/PLAN.md | 7 +++--- docs/PROVENANCE_METHOD.md | 27 ++++++++++++++---------- 4 files changed, 27 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index b74267eb..50314144 100644 --- a/README.md +++ b/README.md @@ -395,9 +395,9 @@ multi-party ceremony, not more SOL. populations is demonstrated as machinery and unproven as a finding. - Not audited. -`docs/MEASUREMENT_LOG.md` records every collection run, including the one that -produced nothing. A measurement project that keeps only its successful runs is -selecting rather than reporting. +`docs/MEASUREMENT_LOG.md` records every collection run, including the three that +produced no headline and the two the tool itself refused. A measurement project +that keeps only its successful runs is selecting rather than reporting. ## Documentation diff --git a/crates/mirror-provenance/src/classify.rs b/crates/mirror-provenance/src/classify.rs index 5095dfd3..226f31e0 100644 --- a/crates/mirror-provenance/src/classify.rs +++ b/crates/mirror-provenance/src/classify.rs @@ -153,8 +153,10 @@ impl Classifier<'_> { /// Whether `facts` terminates a trace, and under which rule. pub fn classify(&self, facts: &AddressFacts) -> Option { // R1 — a program or PDA. Definitional and cheap, and the only thing that - // separates "funded by a protocol vault" from "funded by a person". No - // prior submission in this bounty performs it. + // separates "funded by a protocol vault" from "funded by a person". + // Without it, one exchange hot wallet and one AMM vault land in the same + // class, and the class distribution the headline is computed over is wrong + // in a direction nothing downstream can detect. if let Some(owner) = &facts.owner { if owner != SYSTEM_PROGRAM || facts.executable { return Some(Outcome::Terminal { diff --git a/docs/PLAN.md b/docs/PLAN.md index 7d2085a8..6100aa3c 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -9,9 +9,10 @@ set on a public ledger can be partitioned by where each member's capital came from**, and once you sort a set of `k` members into funding-provenance classes, the anonymity that remains is the size of the actor's class, not `k`. -This is the one problem every serious submission across all three repos of this -bounty concedes and none closes. So it is the problem this contribution takes as -its subject. +It is the problem a privacy pool is most often built around rather than through: +the proof system gets the attention, the funding graph is conceded in a sentence, +and nobody measures what the concession costs. So it is the problem this +contribution takes as its subject. We do not claim to hide provenance. Value moves one way on a ledger, and no deposit pool controls where its users' money came from. What we claim is diff --git a/docs/PROVENANCE_METHOD.md b/docs/PROVENANCE_METHOD.md index 2ee0eb6b..040e358d 100644 --- a/docs/PROVENANCE_METHOD.md +++ b/docs/PROVENANCE_METHOD.md @@ -86,8 +86,8 @@ comparability with the published Ethereum results we benchmark against (§10.3). > A partition is admissible only if the adversary can compute the same label on the > **member side** and on the **action side**. -This rule is load-bearing and it is the single design decision that separates this -work from the prior submissions in this bounty. +This rule is load-bearing, and it is the single design decision the rest of the +method is built around. Entropy over a partition decreases monotonically under refinement. Therefore **finer labels always yield a lower effective-k**, and a *weaker* tracer that fails to merge @@ -510,11 +510,13 @@ For scale: a published 11/30 root-hit rate carries a Wilson 95% CI of - Effective-k has no closed-form CI. Use a **stratified block bootstrap resampling whole clusters** (funder-cluster or slot), `B = 10,000`, BCa intervals. Report the design effect. -- **CI width must be a function of data collected, never of loops run.** A rival's - published interval `[+0.308, +0.325]` is exactly `2·1.96·√(p(1−p)/8000)` where 8,000 - is a `--n` resampling flag default — it can be made arbitrarily tight without - collecting one extra byte. Write a test that fails if the reported CI width changes - when only the resample count changes. +- **CI width must be a function of data collected, never of loops run.** The failure + looks like this: a published interval of `[+0.308, +0.325]` turns out to be exactly + `2·1.96·√(p(1−p)/B)` for `B` the resampling count — so the interval narrows as the + loop count rises and can be made arbitrarily tight without collecting one extra + byte. It is easy to ship because a tighter interval reads as a better measurement. + Write a test that fails if the reported width moves when only the resample count + does. **Entropy bias.** Plug-in `Ĥ` is downward-biased by ≈`(m−1)/(2n ln2)` bits. Emit plug-in, Miller–Madow, and Chao–Shen (coverage-adjusted) side by side. Emit Good–Turing @@ -710,7 +712,8 @@ rule can be ablated in the sensitivity table (§5.5). | — | none fired, budget exhausted | `unresolved(reason, depth, flags)` | **R1 is definitional and cheap** — one `getAccountInfo` — and it is what separates -"funded by a Raydium vault" from "funded by a person". No prior submission performs it. +"funded by a Raydium vault" from "funded by a person". Skip it and those two collapse +into one class, which corrupts the distribution the headline is computed over. **`busy-unlabelled` is not "an attributable origin". Never call it one.** @@ -1302,9 +1305,11 @@ that are not. literally in `accountKeys`. Check explicitly. 4. **Who pays the fee.** If the member submits the transaction even though the PDA executes the action, `accountKeys[0]` *is* the member — total deanonymization. This is - the number-one practical failure mode on Solana, and a rival's own sampler harvests - depositors by exactly this route. A relayer or pool-paid fees is mandatory, and the - design must make member-paid submission **impossible**, not merely discouraged. + the number-one practical failure mode on Solana, and it is how a sampler harvests + depositors from a pool that looks otherwise sound: the fee payer is in the clear on + every transaction, so no proof system anywhere in the design helps. A relayer or + pool-paid fees is mandatory, and the design must make member-paid submission + **impossible**, not merely discouraged. **Honest experiment.** Deploy, execute N actions from N distinct members, run the unmodified tracer, and report per-channel leakage in bits with `provenance = 0.00` sitting From 73c82084019d38aa628ec65377ed90e2c499b953 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 17:20:15 -0300 Subject: [PATCH 54/83] feat: the pool can sign as a member's authority, which we had called impossible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit THREAT_MODEL said the vault "can never be one of the callee's accounts" and gave a reason: this program moves the vault's lamports by direct mutation, and the runtime rejects that account crossing a CPI boundary. The reason is real — `UnbalancedInstruction`, reproduced. The conclusion did not follow. It is a property of the *ordering*, not of the runtime, and nobody had tried the other order. Paying the beneficiary after the call instead of before leaves the vault's balance untouched at the moment of the CPI, and then the vault can be handed to the callee as a signer. The relay fee moves with it, for the same reason. That is the difference between a pool that funds an action and one that can *take* it. A stake delegation or a governance vote needs somebody to sign as the authority, and for a member who must never appear on chain, that somebody can only be the pool. Both orderings cannot hold at once — selector one funds first so the target sees the value, selector two signs — so the member picks, and the selector is inside the action binding. A settler cannot help itself to the pool's signature, and is refused by name when it tries. Evidence, not argument. `the_pool_signs_an_action_as_its_own_authority` asserts it against real SPL Memo, which refuses any account handed to it that has not signed and names its signers in its logs; the test reads that log for the vault's own key, so the claim rests on someone else's program. 30,827 CU. And the attack this invites is tested too. Point the pool's signature at the System Program with a transfer draining the vault: every ingredient legitimate, the payload opaque to us by design, and the runtime kills it with `ExternalAccountLamportSpend`. The attack reaches System — which is what proves the signature was really granted — and dies on the ownership rule. Naming that rule matters, because it stops holding if the vault ever acquires a second owner. Also closes a type confusion found while reading: POOL_VERSION and SPEND_VERSION are both 1 at offset zero, so the version byte discriminates layout revisions and not account kinds. A pool account handed to settlement as a spend record cleared that check and was refused only by a length coincidence deeper in. Spend::load now bounds the size, which no pool can satisfy. 197 tests. submit_spend moved 101,123 -> 101,127 CU and the four-action batch 39,644 -> 39,820; both figures are republished rather than left stale. --- README.md | 44 +++- crates/mirror-cli/src/soak.rs | 166 ++++++++++++-- docs/ARCHITECTURE.md | 34 ++- docs/GROTH16_INTEGRATION.md | 2 +- docs/THREAT_MODEL.md | 63 ++++-- programs/mirror-pool/src/processor.rs | 120 +++++++---- programs/mirror-pool/src/spend.rs | 60 +++++- programs/mirror-pool/tests/end_to_end.rs | 263 ++++++++++++++++++++++- 8 files changed, 656 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 50314144..52ba9376 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,12 @@ it, signed by the pool, batched with everyone else's at one timestamp. An observer sees that a stake, a swap or a vote happened and cannot say which member asked for it. -Moving lamports is the degenerate case, selector zero. The interesting case is -selector one, and the end-to-end suite runs it against a real deployed program: -four members, four memos, one slot, one signer — [see below](#synchronised-actions-are-the-point). +Moving lamports is the degenerate case, selector zero. The interesting cases are +the other two, and the end-to-end suite runs both against a real deployed +program: four members performing one indistinguishable action in a single slot, +and the pool **signing a call as the member's authority** — which is what a stake +delegation or a governance vote needs and what a transfer cannot do. +[See below](#synchronised-actions-are-the-point). Rust end to end. MIT. No Anchor, no Circom, no JavaScript anywhere in the proving path. @@ -66,13 +69,13 @@ not say. There is a section below of things we deliberately do not claim. | | | |---|---| -| `programs/mirror-pool` | The on-chain program. `submit_spend`, proof and all, measured at **101,123 CU** on a real SVM. | +| `programs/mirror-pool` | The on-chain program. `submit_spend`, proof and all, measured at **101,127 CU** on a real SVM. | | `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain. | | `crates/mirror-circuit` | R1CS gadget, membership circuit, prover, key export. | | `crates/mirror-provenance` | The funding-provenance measurement. | | `crates/mirror-cli` | `setup`, `verify-setup`, `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection`, `soak`. | -**193 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**197 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -83,13 +86,14 @@ The brief asks for an anonymity set for *behaviour*, not for funds. That distinction is load-bearing here, so it is tested rather than asserted: ``` -settled 4 real CPI actions in one transaction, 39,644 CU +settled 4 real CPI actions in one transaction, 39,820 CU ``` `a_crowd_of_members_perform_a_real_protocol_action_together` seeds a pool, loads the **real SPL Memo program** into the SVM, and has four members each prove membership and request the identical memo. Settlement invokes Memo four -times in a single transaction, every invocation signed by the pool's vault PDA. +times in a single transaction, every invocation made by the pool and funded from +its vault. What an observer holds afterwards is four identical memos, one timestamp, one signer, and no field anywhere in the transaction that distinguishes which member @@ -97,16 +101,34 @@ asked for which. The actions are indistinguishable **by content** because the payloads match, and indistinguishable **by timing** because settlement gives them one clock. -Two properties make this a behavioural tool rather than a mixer with a CPI bolted -on: +Three properties make this a behavioural tool rather than a mixer with a CPI +bolted on: - **The target is arbitrary.** Selector one invokes any program with any payload. - Staking, voting and swapping are the same code path as the memo; Memo is used - in the test because it is small, real, and validates its own account list. + Memo is used in the test because it is small, real, and validates its own + account list. +- **The pool can sign as your authority.** Selector two hands the pool's vault to + the callee as a *signer*, which is what a stake delegation or a governance vote + needs and what a transfer does not: somebody must sign as the authority, and + for a member who must never appear on chain, that somebody can only be the + pool. `the_pool_signs_an_action_as_its_own_authority` proves it at **30,827 CU** + against real SPL Memo — a program that refuses any account handed to it that + has not signed, and that names its signers in its logs. The test reads that log + for the vault's own key, so the claim rests on someone else's program. - **Moving lamports is the degenerate case.** Selector zero is a plain transfer, kept only because expressing "pay this account" should not require a target program. +The two invoke selectors exist because of an ordering constraint that was +measured rather than assumed. Selector one funds the beneficiary *before* the +call, so the target sees the value; the runtime then refuses to let the vault +cross the CPI boundary, because this program has already moved its lamports by +direct mutation. Selector two pays *after*, leaving the balance untouched at the +moment of the call, which is exactly what lets the vault be a signer. Both +orderings cannot hold at once, so the member chooses, and the selector is inside +the action binding — a settler cannot obtain the pool's signature for a proof +that did not ask for it. + A separate test passes a **non-empty account list** through the CPI, which matters because SPL Memo requires every account handed to it to have signed. If the program dropped an account, mislabelled a signer flag, or miscounted, Memo diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs index 859eff87..7d9fbee7 100644 --- a/crates/mirror-cli/src/soak.rs +++ b/crates/mirror-cli/src/soak.rs @@ -12,7 +12,7 @@ use mirror_core::{Field, MerkleTree, Note}; use mirror_pool_program::{ instruction::Instruction as MirrorIx, pda::{pool_address, spend_address, vault_address}, - processor::SELECTOR_TRANSFER, + processor::{SELECTOR_INVOKE_SIGNED, SELECTOR_TRANSFER}, }; use solana_keypair::Keypair; use solana_program::{ @@ -31,6 +31,28 @@ const ENTRY_FEE: u64 = 0; const K_FLOOR: u32 = 4; const RELAY_FEE: u64 = 200_000; +/// The SPL Memo program, at the same address on every cluster. +/// +/// Used as the live-cluster CPI target for the same reason the end-to-end suite +/// uses it: it is somebody else's program, it is small, and it **refuses any +/// account handed to it that has not signed**. A memo whose only account is the +/// pool's vault therefore cannot succeed unless the pool really signed, which +/// makes the transaction itself the evidence rather than a claim about it. +const MEMO_PROGRAM: &str = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; + +/// What the member asks the pool to say on their behalf. +const MEMO_PAYLOAD: &[u8] = b"mirror-pool: signed by the pool, for a member"; + +/// One member's pending spend, as settlement needs to see it. +pub struct Settlement { + pub spend: Pubkey, + pub beneficiary: Pubkey, + pub relay: Pubkey, + /// The CPI target, when this member's action is a call rather than a + /// transfer. + pub target: Option, +} + /// One recorded step of the run. pub struct Step { pub name: &'static str, @@ -309,6 +331,77 @@ impl Soak { Ok((spend_pda, nullifier)) } + /// Submits a spend whose action is a **CPI the pool signs**, not a transfer. + /// + /// The action declares one account, and settlement will fill that slot with + /// the vault. The count is inside the action binding, so a settler cannot + /// add or drop a slot; the selector is inside it too, so the pool's signature + /// is something the member proved they wanted and not something settlement + /// can help itself to. + #[allow(clippy::too_many_arguments)] + pub fn submit_signed_action( + &mut self, + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + beneficiary: &Pubkey, + relay: &Keypair, + target: &Pubkey, + ) -> Result<(Pubkey, [u8; 32])> { + use ark_std::rand::SeedableRng; + let merkle_proof = tree.proof(index as u64).map_err(|e| anyhow!("{e}"))?; + let binding = mirror_core::action_binding( + SELECTOR_INVOKE_SIGNED, + &target.to_bytes(), + &beneficiary.to_bytes(), + RELAY_FEE, + 1, + MEMO_PAYLOAD, + ); + let witness = Witness { + note: notes[index], + merkle_proof: &merkle_proof, + root: tree.root().map_err(|e| anyhow!("{e}"))?, + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([index as u8 + 1; 32]); + let proof = prove(keys, &witness, &mut rng).map_err(|e| anyhow!("{e}"))?; + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&self.program_id, &self.pool, &nullifier); + + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR_INVOKE_SIGNED, + target_program: target.to_bytes(), + beneficiary: beneficiary.to_bytes(), + relay_fee: RELAY_FEE, + action_accounts: 1, + payload: MEMO_PAYLOAD.to_vec(), + } + .pack(), + vec![ + AccountMeta::new(relay.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let sig = self.send(ix, &[relay])?; + self.record( + "submit_spend", + sig, + format!("note {index}, relay-signed, action: pool-signed CPI to SPL Memo"), + ); + Ok((spend_pda, nullifier)) + } + /// Submits a spend expected to fail, recording the program's error code. #[allow(clippy::too_many_arguments)] pub fn expect_rejection( @@ -327,16 +420,27 @@ impl Soak { } } - pub fn settle(&mut self, batch: &[(Pubkey, Pubkey, Pubkey)]) -> Result<()> { + /// Settles a batch. An entry carrying a target program is a CPI action, and + /// the vault follows it as that action's one account. + pub fn settle(&mut self, batch: &[Settlement]) -> Result<()> { let mut metas = vec![ AccountMeta::new(self.payer.pubkey(), true), AccountMeta::new(self.pool, false), AccountMeta::new(self.vault, false), ]; - for (spend, beneficiary, relay) in batch { - metas.push(AccountMeta::new(*spend, false)); - metas.push(AccountMeta::new(*beneficiary, false)); - metas.push(AccountMeta::new(*relay, false)); + let mut actions = 0usize; + for entry in batch { + metas.push(AccountMeta::new(entry.spend, false)); + metas.push(AccountMeta::new(entry.beneficiary, false)); + metas.push(AccountMeta::new(entry.relay, false)); + if let Some(target) = entry.target { + actions += 1; + metas.push(AccountMeta::new_readonly(target, false)); + // The action's single account. It reaches the callee as a + // *signer* — the program sets that flag itself, from seeds no + // settler holds, which is why this meta says otherwise. + metas.push(AccountMeta::new(self.vault, false)); + } } let ix = Instruction::new_with_bytes( self.program_id, @@ -348,7 +452,15 @@ impl Soak { ); let payer = self.payer.insecure_clone(); let sig = self.send(ix, &[&payer])?; - self.record("settle_epoch", sig, format!("{} spends", batch.len())); + let note = if actions == 0 { + format!("{} spends", batch.len()) + } else { + format!( + "{} spends in one transaction, {actions} of them a CPI the pool signed", + batch.len() + ) + }; + self.record("settle_epoch", sig, note); Ok(()) } @@ -476,20 +588,40 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re let first_fresh = notes.len() - K_FLOOR as usize; // One relay and one beneficiary per member: no member key appears anywhere. + // + // The last member asks for something different from the rest — a CPI the + // pool signs — and it settles in the *same* transaction as the transfers. + // That is the design's own claim about itself: an observer sees one + // timestamp, one signer and a batch of actions, with nothing separating the + // member who staked from the members who merely moved value. + let memo: Pubkey = MEMO_PROGRAM.parse().map_err(|e| anyhow!("memo id: {e}"))?; + let last = first_fresh + K_FLOOR as usize - 1; let mut batch = Vec::new(); for i in first_fresh..first_fresh + K_FLOOR as usize { let beneficiary = Keypair::new().pubkey(); let relay = payer.insecure_clone(); - let (spend, _) = soak.submit_spend( - &keys, - &tree, - ¬es, - i, - &beneficiary, - &relay, - "submit_spend", - )?; - batch.push((spend, beneficiary, relay.pubkey())); + let (spend, target) = if i == last { + let (spend, _) = + soak.submit_signed_action(&keys, &tree, ¬es, i, &beneficiary, &relay, &memo)?; + (spend, Some(memo)) + } else { + let (spend, _) = soak.submit_spend( + &keys, + &tree, + ¬es, + i, + &beneficiary, + &relay, + "submit_spend", + )?; + (spend, None) + }; + batch.push(Settlement { + spend, + beneficiary, + relay: relay.pubkey(), + target, + }); } let vault_before = soak.balance(&soak.vault())?; diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index aca5359c..0e21cd60 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -14,7 +14,8 @@ mirror-core field, Poseidon, Merkle accumulator, notes (linked on-chain mirror-circuit R1CS gadget, membership circuit, prover (host only) mirror-pool the on-chain program mirror-provenance funding-provenance measurement (host only) -mirror-cli setup, verify-setup, endpoint check, seeds, collect, analyze, soak +mirror-cli setup, verify-setup, check-endpoint, seeds, collect, + analyze, compare, selection, soak ``` `mirror-core` is shared by the program and the host deliberately: a commitment, @@ -91,7 +92,7 @@ with root `R`, my nullifier is `H1(k)`, and this proof is bound to `action`.* Three public inputs, and that is a cost decision. On-chain verification measures as `74,179 + 5,661 × N` compute units, so each input costs about 5.7k CU. See `GROTH16_INTEGRATION.md`; the figure this repository reproduces directly is the -whole `submit_spend` instruction at 101,123 CU. +whole `submit_spend` instruction at 101,127 CU. | public input | why it cannot be a witness | |---|---| @@ -153,9 +154,32 @@ The relay signs, never the member. A member paying their own fee would sign with their own wallet and destroy their own anonymity, so no member key appears on chain on this path. -Two limits follow, and `docs/THREAT_MODEL.md` states them: the binding fixes -*how many* accounts an action takes but not *which* ones, and the vault can never -be one of the callee's own accounts. +One limit follows and `docs/THREAT_MODEL.md` states it: the binding fixes *how +many* accounts an action takes but not *which* ones. + +### Funding or signing, and why the member picks + +| selector | | | +|---|---|---| +| 0 | transfer | pay the beneficiary, no CPI | +| 1 | invoke | fund the beneficiary, **then** call | +| 2 | invoke signed | call with the vault as **signer**, then pay | + +Selector two is what lets the pool act as a delegated authority rather than only +as a funder — the thing a stake delegation or a governance vote needs and a +transfer does not. + +The split is forced by the runtime rather than chosen. This program moves the +vault's lamports by direct mutation; an account mutated that way and then handed +across a CPI boundary makes the runtime reject the whole instruction as +`UnbalancedInstruction`. So paying first and signing are mutually exclusive, and +which one an action needs is a property of the action. The selector is inside +the action binding, so the choice belongs to the member and settlement cannot +revise it. + +That constraint was measured, not reasoned about: the earlier design refused the +vault outright and documented the refusal as a property of the runtime. It is a +property of the *ordering*. **`settle_epoch`** — executes a batch in one transaction so every payout shares a timestamp and an ordering. diff --git a/docs/GROTH16_INTEGRATION.md b/docs/GROTH16_INTEGRATION.md index 0aa7549d..56358ce5 100644 --- a/docs/GROTH16_INTEGRATION.md +++ b/docs/GROTH16_INTEGRATION.md @@ -58,7 +58,7 @@ than a slice. Measured in LiteSVM against a real SBF build, with a scratch circuit per input count that is not part of this repository. The figure this repository does -reproduce is the whole `submit_spend` instruction at three inputs — 101,123 CU, +reproduce is the whole `submit_spend` instruction at three inputs — 101,127 CU, printed by `cargo test -p mirror-pool-program --test end_to_end -- --nocapture`. | Public inputs | `verify()` | diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 7681dbe6..7d11fa18 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -32,9 +32,10 @@ rather than transmitted. **Actions carry one caller.** Every action is invoked by the pool program on a member's behalf and funded out of the pool's vault, so the on-chain trace of a -stake made through the pool is identical whoever asked for it. The vault -authorises the CPI through its seeds but is never one of the callee's accounts — -see below. +stake made through the pool is identical whoever asked for it. Under +`SELECTOR_INVOKE_SIGNED` the vault also signs the call, so the pool can be the +*authority* for an action and not only its funder — with a consequence stated +below. **Payouts share a timestamp.** Settlement batches, so arrival time does not separate members within a batch. @@ -93,18 +94,50 @@ that steals escrow. This is a named trust assumption, not a property. `solana program set-upgrade-authority --final` removes it and correspondingly removes the ability to fix anything. -### The pool cannot be one of its own action's accounts - -The vault authorises an action through its seeds, but it is never one of the -callee's accounts. It cannot be: settlement moves the payout out of the vault by -direct mutation before invoking, and handing that same account to a callee makes -the runtime reconcile those lamports across the CPI boundary and reject the whole -instruction as unbalanced — whether the account is marked writable or not. - -So an action whose target needs the pool itself as an account is not expressible -in this version. Value reaches an action through the beneficiary instead. A -settler that tries to place the vault in the action's account list is refused -rather than left to fail later. +### The pool's signature is available to every member + +`SELECTOR_INVOKE_SIGNED` hands the vault to the callee as a signer, so the pool +can act as a delegated authority — a stake authority, a governance voter — +rather than only as a source of funds. That is the capability the behavioural +case needs, and it comes with a property worth stating plainly: + +> **Any member can make the pool sign anything, at any target program.** + +That is safe here only because the vault owns nothing but its own lamports, and +those can be debited by this program alone. The signature therefore grants +authority over nothing. It stops being safe the moment the pool is made an +authority over shared state: make the vault the withdraw authority of a stake +account, and any member can withdraw it. Anyone integrating this pool as an +authority is inheriting that, and no on-chain check here can prevent it, because +the payload is opaque by design. + +The obvious attack is tested rather than argued. +`the_pools_signature_cannot_be_turned_against_its_own_vault` points the pool's +own signature at the System Program with a well-formed transfer draining the +vault to an address the member picked. Every ingredient is legitimate — genuine +proof, owned note, offered selector — and this program never inspects the +payload, so nothing here refuses it. The runtime does: +`ExternalAccountLamportSpend`, *instruction spent from the balance of an account +it does not own*. The attack reaches the System Program, which is the proof that +the signature really was granted, and dies on the ownership rule. That rule is +what the safety rests on, which is worth knowing precisely, because it stops +holding the moment the vault acquires a second owner. + +The ordering constraint behind the two selectors is real and was measured rather +than reasoned about. `SELECTOR_INVOKE` funds the beneficiary *before* invoking, +so the target sees the value; the runtime then rejects the vault crossing the CPI +boundary with `UnbalancedInstruction`, because this program mutated its lamports +directly beforehand. `SELECTOR_INVOKE_SIGNED` pays *after*, which leaves the +balance untouched at the moment of the call. Both orderings cannot hold at once, +so the member picks, and the selector is inside the action binding — a settler +cannot obtain the pool's signature for a proof that did not ask for it, and is +refused by name if it tries. + +`the_pool_signs_an_action_as_its_own_authority` asserts the capability against +the real SPL Memo program, which refuses any account handed to it that has not +signed and names its signers in its logs. The test reads that log for the +vault's own pubkey, so the claim rests on a third-party program's behaviour +rather than on ours. ### The action's account list is chosen by the settler diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 24b3bee3..2692ed5b 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -371,7 +371,7 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest SELECTOR_TRANSFER if req.action_accounts != 0 => { return Err(MirrorProgramError::MalformedInstruction.into()); } - SELECTOR_TRANSFER | SELECTOR_INVOKE => {} + SELECTOR_TRANSFER | SELECTOR_INVOKE | SELECTOR_INVOKE_SIGNED => {} _ => return Err(MirrorProgramError::UnknownSelector.into()), } let binding = mirror_core::action_binding( @@ -452,6 +452,27 @@ pub const SELECTOR_TRANSFER: u64 = 0; /// only moves lamports answers the wrong question. pub const SELECTOR_INVOKE: u64 = 1; +/// Invoke `target_program` with the pool's vault as a **signer** of the call. +/// +/// Selector one funds the beneficiary and then invokes, which is what an action +/// wants when the target must see the value before it acts. The cost is that the +/// vault cannot be one of the callee's accounts: this program has already moved +/// its lamports by direct mutation, and the runtime rejects the whole +/// instruction as `UnbalancedInstruction` when that account then crosses a CPI +/// boundary. +/// +/// This selector pays *after* the invoke instead, which leaves the vault's +/// balance untouched at the moment of the call and lets it be handed to the +/// callee as a signer. That is what a delegated authority needs — a stake +/// account's authority, a governance vote's authority — and it is the difference +/// between a pool that can move lamports on your behalf and one that can *act* +/// on your behalf. +/// +/// The two orderings cannot be combined, so the member picks. The selector is +/// inside the action binding, so the choice is the member's and a settler cannot +/// change it. +pub const SELECTOR_INVOKE_SIGNED: u64 = 2; + /// How long a spend may wait before it can settle alone. /// /// Below the crowd size, a batch must wait this out. It is the escape valve that @@ -553,11 +574,6 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro ) }; - // The relay is paid the same way regardless of what the action is. - if relay_fee > 0 { - move_lamports(vault_account, relay, relay_fee)?; - } - match selector { SELECTOR_TRANSFER => { if action_accounts != 0 { @@ -568,7 +584,7 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro // nested invoke on the hot path. move_lamports(vault_account, beneficiary, payout)?; } - SELECTOR_INVOKE => { + SELECTOR_INVOKE | SELECTOR_INVOKE_SIGNED => { // The target program's own account comes first, because a CPI // requires the callee to be present in the caller's account // list. Its key is checked against the record, so a settler @@ -590,10 +606,24 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro target_info, payout, &action_infos, + selector == SELECTOR_INVOKE_SIGNED, )?; } _ => return Err(MirrorProgramError::UnknownSelector.into()), } + + // The relay is paid after the action, never before. + // + // Under `SELECTOR_INVOKE_SIGNED` the vault is one of the callee's + // accounts, and any direct lamport mutation this program makes to it + // before that call makes the runtime reject the whole instruction as + // `UnbalancedInstruction`. Paying the relay here rather than above the + // match is what keeps the vault's balance untouched at the moment of + // the CPI. The other selectors do not care about the order, so there is + // one order rather than two. + if relay_fee > 0 { + move_lamports(vault_account, relay, relay_fee)?; + } settled += 1; } @@ -616,12 +646,17 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro Ok(()) } -/// Invokes the member's chosen program on their behalf, signed by the vault. +/// Invokes the member's chosen program on their behalf. +/// +/// The call is made by this program and the value comes out of the pool's vault, +/// so from the chain's point of view the action was taken by the pool. Every +/// member's action looks the same from outside, which is what makes it +/// unattributable: the on-chain trace of a stake made through this pool is +/// identical whoever asked for it. /// -/// The vault PDA is the signer, so from the chain's point of view the action was -/// taken by the pool. Every member's action carries the same signer, which is -/// what makes an action unattributable: the on-chain trace of a stake made -/// through this pool is identical whoever asked for it. +/// Under `SELECTOR_INVOKE_SIGNED` the vault is additionally a *signer* of the +/// call, so the pool can act as a delegated authority rather than only as a +/// source of funds. /// /// The payload and the target were both fixed at submit time and bound into the /// proof, so a settler chooses neither, and the declared account count is bound @@ -630,9 +665,9 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro /// for any caller. For a target whose destination is an account rather than /// instruction data, that is a real limit, and the threat model says so. /// -/// The vault is appended by this program as the final account and marked signer. -/// A settler that tries to place it in the action's own list is refused, because -/// the same account appearing twice has its lamport change applied twice. +/// Whether the vault may be one of the callee's own accounts depends on +/// `pool_signs`, and the reason is lamport ordering rather than taste — see +/// `SELECTOR_INVOKE_SIGNED`. #[allow(clippy::too_many_arguments)] fn invoke_action<'a>( program_id: &Pubkey, @@ -643,6 +678,7 @@ fn invoke_action<'a>( target_info: &AccountInfo<'a>, payout: u64, action_infos: &[AccountInfo<'a>], + pool_signs: bool, ) -> ProgramResult { let (target, payload) = { let mut data = spend_account.try_borrow_mut_data()?; @@ -666,32 +702,31 @@ fn invoke_action<'a>( return Err(MirrorProgramError::InvalidPda.into()); } - // Fund the action before invoking, so the target sees the value it is meant - // to act on. The vault signs, so the funds visibly come from the pool. - move_lamports(vault_account, beneficiary, payout)?; - - // The vault authorises the call through its seeds but is deliberately not - // one of the callee's accounts. - // - // This program has already moved the payout out of the vault by direct - // mutation. Handing that same account to a callee makes the runtime - // reconcile those lamports across the CPI boundary, and it rejects the whole - // instruction as unbalanced — with the vault marked writable or not. So the - // vault reaches `invoke_signed` in the account infos, which is what lets its - // seeds sign, and never appears in the instruction's account list. - // - // The consequence is a real limit, and THREAT_MODEL.md states it: an action - // whose target needs the pool itself as one of its accounts is not - // expressible here. Value reaches the action through the beneficiary. - if action_infos.iter().any(|a| a.key == vault_account.key) { - return Err(MirrorProgramError::MalformedInstruction.into()); + // Under `SELECTOR_INVOKE` the vault must not be one of the callee's + // accounts, and this is a runtime constraint rather than a policy: the + // payout below moves the vault's lamports by direct mutation, and an account + // mutated that way then handed across a CPI boundary makes the runtime + // reject the whole instruction as `UnbalancedInstruction`. Refusing it here + // turns that into a named error instead of an opaque runtime failure. + if !pool_signs { + if action_infos.iter().any(|a| a.key == vault_account.key) { + return Err(MirrorProgramError::MalformedInstruction.into()); + } + // Fund the action before invoking, so the target sees the value it is + // meant to act on. + move_lamports(vault_account, beneficiary, payout)?; } + // The vault's signature is granted by `invoke_signed` below, but only for + // accounts that appear in the instruction's own list — an account passed in + // the infos and absent from the metas is ignored by the callee, signer seeds + // or not. So under `SELECTOR_INVOKE_SIGNED` the flag has to be set here, on + // the meta, for the callee to see the pool as a signer at all. let metas: Vec = action_infos .iter() .map(|a| solana_program::instruction::AccountMeta { pubkey: *a.key, - is_signer: a.is_signer, + is_signer: a.is_signer || (pool_signs && a.key == vault_account.key), is_writable: a.is_writable, }) .collect(); @@ -708,7 +743,9 @@ fn invoke_action<'a>( // list twice, and the runtime then reconciles its lamports against itself // and fails the whole instruction as unbalanced. let mut infos = action_infos.to_vec(); - infos.push(vault_account.clone()); + if !infos.iter().any(|a| a.key == vault_account.key) { + infos.push(vault_account.clone()); + } if !infos.iter().any(|a| a.key == target_info.key) { infos.push(target_info.clone()); } @@ -717,7 +754,16 @@ fn invoke_action<'a>( &ix, &infos, &[&[VAULT_SEED, pool_account.key.as_ref(), &[vault_bump]]], - ) + )?; + + // The payout happens *after* the call on this path, which is the whole + // reason the vault could be handed to the callee as a signer. The + // beneficiary is paid either way before this instruction returns, so the + // ordering changes what the callee observes and nothing about what is owed. + if pool_signs { + move_lamports(vault_account, beneficiary, payout)?; + } + Ok(()) } /// Moves lamports between two accounts this program owns or may credit. diff --git a/programs/mirror-pool/src/spend.rs b/programs/mirror-pool/src/spend.rs index bf57f6d8..82aef1a0 100644 --- a/programs/mirror-pool/src/spend.rs +++ b/programs/mirror-pool/src/spend.rs @@ -57,7 +57,21 @@ pub struct Spend<'a> { impl<'a> Spend<'a> { pub fn load(data: &'a mut [u8]) -> Result { - if data.len() < SPEND_BASE_LEN || data[offset::VERSION] != SPEND_VERSION { + // The upper bound is what stops this from being a type confusion. + // + // `POOL_VERSION` and `SPEND_VERSION` are both 1 and both live at offset + // zero, so the version byte distinguishes *layout revisions* and not + // account *kinds*: a pool account passed where a spend record is + // expected clears the version check. Settlement's other checks — + // `record.pool()` against the pool key — would then have to be read out + // of Merkle frontier bytes and are what actually refuse it, which is a + // coincidence to depend on rather than a rule. No spend record is ever + // larger than this, and every other account this program owns is, so + // one comparison makes the confusion unrepresentable. + if data.len() < SPEND_BASE_LEN + || data.len() > spend_len(MAX_PAYLOAD) + || data[offset::VERSION] != SPEND_VERSION + { return Err(MirrorProgramError::InvalidSpendAccount); } let spend = Spend { data }; @@ -341,4 +355,48 @@ mod tests { assert_eq!(s.target_program(), [0x12; 32]); assert_eq!(s.action_accounts(), 7); } + + /// A pool account must never load as a spend record. + /// + /// Both carry version 1 at offset zero, so the version byte cannot tell them + /// apart. This pins the size bound that does. The value is not + /// `POOL_LEN` by import but by construction: any account bigger than the + /// largest possible record is refused, whatever it happens to be. + #[test] + fn an_account_too_large_to_be_a_record_is_refused() { + let mut pool_sized = vec![0u8; 4792]; + pool_sized[offset::VERSION] = SPEND_VERSION; + // Declare exactly the payload length that makes the arithmetic close, + // which is what a colliding account would have to do. + let declared = (4792 - SPEND_BASE_LEN) as u16; + pool_sized[offset::PAYLOAD_LEN..offset::PAYLOAD_LEN + 2] + .copy_from_slice(&declared.to_le_bytes()); + assert!( + Spend::load(&mut pool_sized).is_err(), + "an account far larger than any record loaded as one" + ); + } + + #[test] + fn the_largest_legal_record_still_loads() { + let mut data = vec![0u8; spend_len(MAX_PAYLOAD)]; + { + let mut spend = Spend::load_uninitialised(&mut data).unwrap(); + spend + .initialise( + 1, + 0, + 0, + 0, + &[0; 32], + &[0; 32], + &[0; 32], + &[0; 32], + 0, + &[7u8; MAX_PAYLOAD], + ) + .unwrap(); + } + assert_eq!(Spend::load(&mut data).unwrap().payload().len(), MAX_PAYLOAD); + } } diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index 3f974f43..9ceafdd7 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -885,6 +885,7 @@ fn a_member_can_always_exit_without_any_relay() { // --------------------------------------------------------------------------- const INVOKE: u64 = mirror_pool_program::processor::SELECTOR_INVOKE; +const INVOKE_SIGNED: u64 = mirror_pool_program::processor::SELECTOR_INVOKE_SIGNED; /// A spend that asks the pool to invoke `target` with `payload`. fn action_ix( @@ -918,6 +919,31 @@ fn action_ix_n( relay: &Pubkey, action_accounts: u8, payload: &[u8], +) -> Instruction { + action_ix_sel( + env, + proof, + nullifier, + target, + beneficiary, + relay, + action_accounts, + payload, + INVOKE, + ) +} + +#[allow(clippy::too_many_arguments)] +fn action_ix_sel( + env: &Env, + proof: &mirror_circuit::SolanaProof, + nullifier: [u8; 32], + target: &Pubkey, + beneficiary: &Pubkey, + relay: &Pubkey, + action_accounts: u8, + payload: &[u8], + selector: u64, ) -> Instruction { let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); Instruction::new_with_bytes( @@ -928,7 +954,7 @@ fn action_ix_n( proof_c: proof.proof_c, root: proof.public_inputs[0], nullifier, - selector: INVOKE, + selector, target_program: target.to_bytes(), beneficiary: beneficiary.to_bytes(), relay_fee: RELAY_FEE, @@ -967,11 +993,36 @@ fn action_proof_n( beneficiary: &Pubkey, action_accounts: u8, payload: &[u8], +) -> mirror_circuit::SolanaProof { + action_proof_sel( + keys, + tree, + notes, + index, + target, + beneficiary, + action_accounts, + payload, + INVOKE, + ) +} + +#[allow(clippy::too_many_arguments)] +fn action_proof_sel( + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + target: &Pubkey, + beneficiary: &Pubkey, + action_accounts: u8, + payload: &[u8], + selector: u64, ) -> mirror_circuit::SolanaProof { use ark_std::rand::SeedableRng; let merkle_proof = tree.proof(index as u64).unwrap(); let binding = mirror_core::action_binding( - INVOKE, + selector, &target.to_bytes(), &beneficiary.to_bytes(), RELAY_FEE, @@ -989,8 +1040,9 @@ fn action_proof_n( } /// The thesis, end to end: a crowd of members each perform the *same shape* of -/// protocol action, the pool invokes the target program for every one of them in -/// a single transaction, and every invocation carries the pool as its signer. +/// protocol action, and the pool invokes the target program for every one of +/// them in a single transaction — one timestamp, one signer on the transaction, +/// nothing distinguishing which member asked for which. /// /// An observer sees four memos land at one timestamp, signed by one pool, and /// has nothing in the transaction that distinguishes which member asked for @@ -1068,8 +1120,6 @@ fn an_action_runs_with_a_non_empty_account_list() { env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); let payload = b"signed by the pool".as_slice(); - // One action account: the vault itself, which Memo will require to have - // signed. // One action account, declared in the proof and therefore bound. let proof = action_proof_n(&keys, &tree, ¬es, 0, &memo, &beneficiary, 1, payload); let nullifier = proof.public_inputs[1]; @@ -1166,9 +1216,15 @@ fn a_settler_cannot_change_how_many_accounts_an_action_gets() { ); } -/// The vault can never be one of the callee's accounts, and a settler that tries -/// is refused rather than left to fail deeper in the runtime. THREAT_MODEL.md -/// states this; without a test it was the one claim there resting on reading. +/// The pool's signature is the member's decision and a settler cannot help +/// themselves to it. +/// +/// Under plain `INVOKE` the payout leaves the vault *before* the call, so the +/// vault cannot cross the CPI boundary — the runtime would reject the whole +/// instruction as unbalanced. The program refuses it by name instead, which +/// turns an opaque runtime failure into a stated rule. A member who wants the +/// pool to sign proves `INVOKE_SIGNED`, and the selector is inside the action +/// binding, so this is not something settlement can decide. #[test] fn a_settler_cannot_place_the_vault_in_an_action_account_list() { let (mut env, tree, notes, keys) = seeded_pool(6); @@ -1219,6 +1275,195 @@ fn a_settler_cannot_place_the_vault_in_an_action_account_list() { ); } +/// The pool acting as a delegated **authority**, not merely as a source of funds. +/// +/// This is the capability a stake delegation or a governance vote needs and a +/// plain transfer does not: somebody has to sign as the authority, and for a +/// member who must never appear on chain that somebody can only be the pool. +/// +/// SPL Memo is the witness, and it is a good one because it is a real third-party +/// program that refuses any account handed to it that has not signed, and because +/// it *names* its signers in its logs. So a memo whose only account is the pool's +/// vault cannot pass by accident: either Memo rejects the instruction, or it +/// prints the vault's own pubkey. The assertion below reads that log. +/// +/// Note what the settler supplies: the vault marked **not** a signer, because a +/// PDA has no key and cannot sign a transaction. The signature comes from +/// `invoke_signed` inside the program, from seeds only this program holds. +#[test] +fn the_pool_signs_an_action_as_its_own_authority() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let memo: Pubkey = MEMO_PROGRAM.parse().unwrap(); + env.svm.add_program(memo, &memo_bytes()).unwrap(); + + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let payload = b"the pool signed this".as_slice(); + + let proof = action_proof_sel( + &keys, + &tree, + ¬es, + 0, + &memo, + &beneficiary, + 1, + payload, + INVOKE_SIGNED, + ); + let nullifier = proof.public_inputs[1]; + let ix = action_ix_sel( + &env, + &proof, + nullifier, + &memo, + &beneficiary, + &relay.pubkey(), + 1, + payload, + INVOKE_SIGNED, + ); + env.send(ix, &relay).expect("submitting the signed action"); + + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let mut clock = env.svm.get_sysvar::(); + clock.unix_timestamp += mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS + 1; + env.svm.set_sysvar(&clock); + + let vault_before = env.vault_lamports(); + let batch = vec![(spend_pda, beneficiary, relay.insecure_clone())]; + let ix = settle_ix_full( + &env, + &batch, + &settler.pubkey(), + &[Some(memo)], + &[vec![AccountMeta::new(env.vault, false)]], + ); + let msg = Message::new(&[ix], Some(&settler.pubkey())); + let tx = Transaction::new(&[&settler], msg, env.svm.latest_blockhash()); + let meta = env + .svm + .send_transaction(tx) + .unwrap_or_else(|e| panic!("settling a signed action: {:?}\n{:#?}", e.err, e.meta.logs)); + + let vault = env.vault.to_string(); + assert!( + meta.logs + .iter() + .any(|l| l.contains("Signed by") && l.contains(&vault)), + "SPL Memo did not report the pool's vault as a signer, so the pool did \ + not actually sign: {:#?}", + meta.logs + ); + println!( + "the pool signed a real CPI as authority, {} CU", + meta.compute_units_consumed + ); + + // The member is still paid in full, and the payout still leaves the vault — + // the reordering changes when the callee sees the value, not who is owed it. + assert_eq!( + env.svm.get_account(&beneficiary).unwrap().lamports, + DENOMINATION - RELAY_FEE + ); + assert_eq!(vault_before - env.vault_lamports(), DENOMINATION); + + let mut data = env.svm.get_account(&spend_pda).unwrap().data; + assert_eq!(Spend::load(&mut data).unwrap().status(), STATUS_SETTLED); +} + +/// The attack the signing selector invites: point the pool's own signature at +/// the System Program and tell it to move the escrow somewhere else. +/// +/// Every ingredient is legitimate. The proof is genuine, the member owns the +/// note, the selector is one the protocol offers, and the payload is a +/// well-formed `SystemInstruction::Transfer` draining the vault to an address +/// the member chose. Nothing in this program inspects the payload — it cannot, +/// the payload is opaque by design — so what refuses this is the ownership rule +/// underneath: the System Program may only debit accounts it owns, and the vault +/// is owned by the pool. +/// +/// That is the reason the pool's signature is safe to hand out. It is worth a +/// test rather than a sentence, because the sentence is the kind that stays true +/// only until somebody gives the vault a second owner or a data layout. +#[test] +fn the_pools_signature_cannot_be_turned_against_its_own_vault() { + let (mut env, tree, notes, keys) = seeded_pool(6); + let system = solana_system_interface::program::ID; + + let attacker = Pubkey::new_unique(); + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + + // SystemInstruction::Transfer { lamports }: discriminant 2 as u32, then the + // amount. Drain everything the vault is holding for the other members. + let mut payload = 2u32.to_le_bytes().to_vec(); + payload.extend_from_slice(&(DENOMINATION * 5).to_le_bytes()); + + let proof = action_proof_sel( + &keys, + &tree, + ¬es, + 1, + &system, + &beneficiary, + 2, + &payload, + INVOKE_SIGNED, + ); + let nullifier = proof.public_inputs[1]; + let ix = action_ix_sel( + &env, + &proof, + nullifier, + &system, + &beneficiary, + &relay.pubkey(), + 2, + &payload, + INVOKE_SIGNED, + ); + env.send(ix, &relay).expect("submitting the drain attempt"); + + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let mut clock = env.svm.get_sysvar::(); + clock.unix_timestamp += mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS + 1; + env.svm.set_sysvar(&clock); + + let vault_before = env.vault_lamports(); + let batch = vec![(spend_pda, beneficiary, relay.insecure_clone())]; + let ix = settle_ix_full( + &env, + &batch, + &settler.pubkey(), + &[Some(system)], + &[vec![ + AccountMeta::new(env.vault, false), + AccountMeta::new(attacker, false), + ]], + ); + let err = env + .send(ix, &settler) + .expect_err("the pool signed away its own escrow"); + println!("vault drain via the pool's own signature rejected: {err}"); + + // The whole transaction reverted, so nothing moved and the note is unspent. + assert_eq!(env.vault_lamports(), vault_before); + assert_eq!(env.svm.get_account(&attacker).map(|a| a.lamports), None); + let mut data = env.svm.get_account(&spend_pda).unwrap().data; + assert_eq!( + Spend::load(&mut data).unwrap().status(), + mirror_pool_program::spend::STATUS_PENDING, + "the spend was consumed by an attack that failed" + ); +} + #[test] fn a_relay_cannot_swap_the_target_program() { let (mut env, tree, notes, keys) = seeded_pool(6); From b49bbbea9a85b626a92228d5bdbc9ce5aa522f1e Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 17:44:13 -0300 Subject: [PATCH 55/83] fix: a signed action could not settle in a batch, which devnet found and the tests did not MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signing selector shipped with a test that settled it alone, and that shape is the one shape the bug does not appear in. On devnet, with three transfers ahead of it in the batch, settlement failed: UnbalancedInstruction. The runtime's objection is not to a payout preceding *that member's* call. It is to any lamport this program moved anywhere in the same instruction, which in a batch means the members settled before it. So the constraint is a property of the batch, and one pass over the spends cannot satisfy it. Settlement now reads the whole batch, runs every call the pool signs while the vault is still untouched, and pays everyone afterwards. Selector one keeps funding before its own call, because it needs to and can: it never hands the vault to the callee. The shape is the point, not just the fix. A crowd is mixed by definition, and a signed action that could only settle alone would have to wait out the timeout instead of joining a batch — giving up the shared timestamp that is the reason to stand in the crowd at all. Devnet carries it now. The settlement in PROOF.md is four spends in one transaction, one of them the pool invoking SPL Memo as a member's authority, and the evidence is Memo's own log naming the vault as a signer. The soak reads that line back off the cluster and fails the run if it is absent, so the section cannot be published without the callee having said it. Two soak defects found while getting there, both of which cost a run: - The note ledger was one shared file while the pool is per-denomination, so the guard that says "use a fresh denomination" produced a ledger that disagreed with the fresh chain. The ledger is keyed by denomination now. - Reusing a pool that still held unspent notes ran the whole positive path and then failed at the accounting assert, after the deposits had been spent to get there. It is refused up front instead, with the reason. 198 tests. Redeployed to devnet at the same program id, PROOF.md regenerated. --- README.md | 14 ++- crates/mirror-cli/src/chain.rs | 29 +++++ crates/mirror-cli/src/soak.rs | 101 +++++++++++++++-- data/soak-notes-20000011.json | 1 + data/soak-notes-20000013.json | 1 + data/soak-notes-20000017.json | 1 + docs/ARCHITECTURE.md | 4 +- docs/PROOF.md | 46 +++++--- programs/mirror-pool/src/processor.rs | 133 +++++++++++++++++------ programs/mirror-pool/tests/end_to_end.rs | 93 ++++++++++++++++ 10 files changed, 357 insertions(+), 66 deletions(-) create mode 100644 data/soak-notes-20000011.json create mode 100644 data/soak-notes-20000013.json create mode 100644 data/soak-notes-20000017.json diff --git a/README.md b/README.md index 52ba9376..0685fa56 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ not say. There is a section below of things we deliberately do not claim. | `crates/mirror-provenance` | The funding-provenance measurement. | | `crates/mirror-cli` | `setup`, `verify-setup`, `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection`, `soak`. | -**197 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**198 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -374,9 +374,17 @@ lifecycle ran there against a real validator — pool creation, deposits, spends each carrying a Groth16 proof verified by the deployed program's own syscall, and a settlement that closed the vault to its rent-exempt minimum to the lamport. -`docs/PROOF.md` has every signature *and* the lamports, because "closed to the +That settlement carried four spends and **one of them was not a transfer**: the +pool invoked SPL Memo as that member's authority, in the same transaction as the +other three. A signature only proves a transaction landed, so the evidence comes +from the callee — Memo names its signers, and it named the pool's vault, an +account with no private key. The soak reads that line back off the cluster and +fails the run if it is missing, so `docs/PROOF.md` cannot carry the claim +without the claim being true. + +That file has every signature *and* the lamports, because "closed to the rent-exempt minimum" is the interesting part of that sentence and a list of -signatures does not show it: 80,000,028 owed against 80,000,028 paid out, and a +signatures does not show it: 80,000,068 owed against 80,000,068 paid out, and a vault resting on its floor with a remainder of zero. The soak asserts both and fails the run otherwise, so that table cannot record a discrepancy and still exit successfully. diff --git a/crates/mirror-cli/src/chain.rs b/crates/mirror-cli/src/chain.rs index eac7b657..a2a25253 100644 --- a/crates/mirror-cli/src/chain.rs +++ b/crates/mirror-cli/src/chain.rs @@ -153,6 +153,35 @@ impl Chain { Ok(Some(base64::engine::general_purpose::STANDARD.decode(b64)?)) } + /// The log lines a landed transaction produced. + /// + /// Used to read what a *callee* said about a CPI, which is the only way to + /// check a claim about the inner call from outside: the outer signature + /// proves the transaction landed and says nothing about who signed the + /// instruction the pool made inside it. + pub fn transaction_logs(&self, signature: &str) -> Result> { + let v = self.call( + "getTransaction", + serde_json::json!([ + signature, + { "commitment": "confirmed", "maxSupportedTransactionVersion": 0 } + ]), + )?; + if v.is_null() { + return Err(anyhow!( + "getTransaction: {signature} is not visible yet on this endpoint" + )); + } + let logs = v + .pointer("/meta/logMessages") + .and_then(|l| l.as_array()) + .ok_or_else(|| anyhow!("getTransaction: {signature} carries no log messages"))?; + Ok(logs + .iter() + .filter_map(|l| l.as_str().map(str::to_owned)) + .collect()) + } + pub fn balance(&self, key: &Pubkey) -> Result { let v = self.call( "getBalance", diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs index 7d9fbee7..88a30306 100644 --- a/crates/mirror-cli/src/soak.rs +++ b/crates/mirror-cli/src/soak.rs @@ -26,7 +26,7 @@ use solana_transaction::Transaction; // pool. Changing it starts a clean one, which is how the evidence run gets to // record its own `init_pool` rather than reusing a pool an earlier run created // and leaving the creation step undocumented. -const DENOMINATION: u64 = 20_000_007; // 0.02 SOL +const DENOMINATION: u64 = 20_000_017; // 0.02 SOL const ENTRY_FEE: u64 = 0; const K_FLOOR: u32 = 4; const RELAY_FEE: u64 = 200_000; @@ -105,6 +105,8 @@ pub struct Soak { /// Case name, the program's own error code, and what the rejection proves. pub negatives: Vec<(String, String, String)>, pub accounting: Option, + /// The callee's own log line naming the pool's vault as a signer. + pub signed_action: Option, } impl Soak { @@ -120,6 +122,7 @@ impl Soak { steps: Vec::new(), negatives: Vec::new(), accounting: None, + signed_action: None, } } @@ -169,9 +172,32 @@ impl Soak { } /// Creates the pool, unless a previous run already did. + /// + /// Reusing a pool is allowed only while it is empty. The accounting claim + /// this run publishes — that the vault came to rest on its rent-exempt floor + /// with a remainder of zero — is a statement about a pool whose every + /// deposit this run also settles. A pool carrying notes from an earlier run + /// cannot satisfy it, and finding that out at the assertion means having + /// already spent the deposits to get there. It is checked before the money + /// moves instead. pub fn init_pool(&mut self) -> Result<()> { - if self.client.account_data(&self.pool)?.is_some() { - println!(" pool already exists, reusing it"); + if let Some(data) = self.client.account_data(&self.pool)? { + let mut data = data; + let pool = mirror_pool_program::Pool::load(&mut data) + .map_err(|e| anyhow!("the account at the pool address is not a pool: {e:?}"))?; + let outstanding = pool + .outstanding_notes() + .map_err(|e| anyhow!("reading the pool: {e:?}"))?; + if outstanding > 0 { + return Err(anyhow!( + "the pool for denomination {DENOMINATION} already holds {outstanding} \ + unspent note(s), so this run cannot close its vault to the rent-exempt \ + floor and the accounting table would be a different claim than the one \ + it makes. Raise DENOMINATION in soak.rs for a clean pool — the note \ + ledger is keyed by it, so a new denomination starts a new ledger too." + )); + } + println!(" pool already exists and is empty, reusing it"); return Ok(()); } let ix = Instruction::new_with_bytes( @@ -420,9 +446,36 @@ impl Soak { } } + /// Reads the callee's own logs to confirm the pool signed the inner call. + /// + /// The settle signature proves a transaction landed. It says nothing about + /// who signed the instruction the pool made *inside* it, and that is the + /// claim worth checking, so this asks the cluster for the logs and looks for + /// what SPL Memo says. Memo names every signer it was given and refuses any + /// account that has not signed, so its own words are the evidence — and if + /// they are absent the run fails here rather than publishing the claim. + pub fn confirm_pool_signed(&mut self, signature: &str) -> Result<()> { + let logs = self.client.transaction_logs(signature)?; + let vault = self.vault.to_string(); + let line = logs + .iter() + .find(|l| l.contains("Signed by") && l.contains(&vault)) + .ok_or_else(|| { + anyhow!( + "settlement landed but SPL Memo did not report the pool's vault \ + ({vault}) as a signer, so the pool did not actually sign. Logs:\n{}", + logs.join("\n") + ) + })? + .clone(); + println!(" the callee's own log: {}", line.trim()); + self.signed_action = Some(line); + Ok(()) + } + /// Settles a batch. An entry carrying a target program is a CPI action, and /// the vault follows it as that action's one account. - pub fn settle(&mut self, batch: &[Settlement]) -> Result<()> { + pub fn settle(&mut self, batch: &[Settlement]) -> Result { let mut metas = vec![ AccountMeta::new(self.payer.pubkey(), true), AccountMeta::new(self.pool, false), @@ -460,8 +513,8 @@ impl Soak { batch.len() ) }; - self.record("settle_epoch", sig, note); - Ok(()) + self.record("settle_epoch", sig.clone(), note); + Ok(sig) } pub fn pool(&self) -> Pubkey { @@ -579,7 +632,14 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re println!("\npositive path:"); soak.init_pool()?; - let ledger = std::path::Path::new("data/soak-notes.json"); + + // The ledger is scoped to the denomination because the pool is. `deposit` + // refuses to run when the ledger and the chain disagree on how many leaves + // exist, and tells you to use a fresh denomination — advice that a single + // shared ledger file would make impossible to follow, since the fresh pool + // starts empty and the old notes are still in the file. + let ledger_path = format!("data/soak-notes-{DENOMINATION}.json"); + let ledger = std::path::Path::new(&ledger_path); let (tree, notes) = soak.deposit(K_FLOOR as u64, ledger)?; // Spend the notes this run deposited, not the earliest ones. A rerun @@ -625,7 +685,8 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re } let vault_before = soak.balance(&soak.vault())?; - soak.settle(&batch)?; + let settle_sig = soak.settle(&batch)?; + soak.confirm_pool_signed(&settle_sig)?; let vault_after = soak.balance(&soak.vault())?; // The vault carries no data, so its floor is the rent-exempt minimum for a // zero-byte account — asked of the cluster rather than assumed. @@ -805,6 +866,30 @@ fn write_proof(soak: &Soak, url: &str, out: &std::path::Path) -> Result<()> { )); } + if let Some(line) = &soak.signed_action { + md.push_str("\n## The pool signed an action, and the callee said so\n\n"); + md.push_str( + "The settlement above carried four spends, and one of them was not a transfer: \ + the pool invoked SPL Memo as that member's **authority**, in the same \ + transaction as the other three. That is the capability a stake delegation or a \ + governance vote needs and a payment does not.\n\n", + ); + md.push_str( + "A signature only proves the transaction landed. It says nothing about who \ + signed the instruction the pool made *inside* it, so the evidence has to come \ + from the callee. SPL Memo refuses any account handed to it that has not signed, \ + and names the ones that did:\n\n", + ); + md.push_str(&format!("```\n{}\n```\n\n", line.trim())); + md.push_str(&format!( + "That is the pool's vault, `{}`, which has no private key — it signed through \ + seeds only the program holds. The soak reads this line back from the cluster \ + and fails the run if it is absent, so this section cannot appear without the \ + callee having said it.\n", + soak.vault() + )); + } + md.push_str("\n## Rejections\n\n"); md.push_str( "A negative case is only evidence if the program's own error code is what \ diff --git a/data/soak-notes-20000011.json b/data/soak-notes-20000011.json new file mode 100644 index 00000000..dfb5abe0 --- /dev/null +++ b/data/soak-notes-20000011.json @@ -0,0 +1 @@ +[[1785102932292732,14136187712264],[1785102932292739,14136187712277],[1785102932292746,14136187712290],[1785102932292753,14136187712303],[1785103404294148,14136191450032],[1785103404294155,14136191450045],[1785103404294162,14136191450058],[1785103404294169,14136191450071]] \ No newline at end of file diff --git a/data/soak-notes-20000013.json b/data/soak-notes-20000013.json new file mode 100644 index 00000000..8950153d --- /dev/null +++ b/data/soak-notes-20000013.json @@ -0,0 +1 @@ +[[1785103497294427,14136192186499],[1785103497294434,14136192186512],[1785103497294441,14136192186525],[1785103497294448,14136192186538],[1785103518294490,14136192352798]] \ No newline at end of file diff --git a/data/soak-notes-20000017.json b/data/soak-notes-20000017.json new file mode 100644 index 00000000..d8866ebd --- /dev/null +++ b/data/soak-notes-20000017.json @@ -0,0 +1 @@ +[[1785103732295132,14136194047464],[1785103732295139,14136194047477],[1785103732295146,14136194047490],[1785103732295153,14136194047503],[1785103754295198,14136194221682]] \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 0e21cd60..ccd20672 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -79,8 +79,8 @@ credited to it, and pool creation refuses a nonzero entry fee, so there is no second category of lamports anywhere that could be mistaken for backing for an unspent note. -`docs/PROOF.md` carries the devnet numbers: four notes settled, 80,000,028 -lamports owed and 80,000,028 paid, and a vault that came to rest on its +`docs/PROOF.md` carries the devnet numbers: four notes settled, 80,000,068 +lamports owed and 80,000,068 paid, and a vault that came to rest on its rent-exempt floor with a remainder of zero. The soak asserts that rather than printing it, so a run that disagreed would fail instead of publishing. diff --git a/docs/PROOF.md b/docs/PROOF.md index a6877d92..f450b4e5 100644 --- a/docs/PROOF.md +++ b/docs/PROOF.md @@ -4,24 +4,24 @@ Every line here is a transaction that landed. Nothing is simulated: the proof is - cluster: `devnet` - program: `8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa` -- pool: `J68VyjjKQTvg7tsZJHoCEQ5nMYGnUVs4esA7EF1nP8Ui` -- vault: `54PT56YwQWJA6UAYNsnqKPJq4SrCW2gnF7HVvU8QDk7a` +- pool: `AM7MrJGDWj83poTTRK4gVpCuGgybUKzNvZRW41x3yCfJ` +- vault: `3x8NUzCzQdbjEX2T7gapDAgg6nJU8Y3ZsbUFPnqfhyLV` ## Flows | step | signature | note | |---|---|---| -| init_pool | [`2i2dSth52MNMboBjL7s28MaArrh9Vmj2z7Rm21CYpig79czESepPUyqkDPiyvM8UvV2NP587kyZUqxhZYB2CJrdB`](https://explorer.solana.com/tx/2i2dSth52MNMboBjL7s28MaArrh9Vmj2z7Rm21CYpig79czESepPUyqkDPiyvM8UvV2NP587kyZUqxhZYB2CJrdB?cluster=devnet) | denomination 20000007, k_floor 4 | -| deposit | [`3uFCoMKsmeyWFbvRFE2qiwWm3VRTZmdMW4tnsSxLMCMuXfhzrt7irdjE9S1R86HmzC3vc7j1BKpNbj893CrzW8RX`](https://explorer.solana.com/tx/3uFCoMKsmeyWFbvRFE2qiwWm3VRTZmdMW4tnsSxLMCMuXfhzrt7irdjE9S1R86HmzC3vc7j1BKpNbj893CrzW8RX?cluster=devnet) | note 1 | -| deposit | [`4dtLcdt9CdCdoimcgNLouG8Kh72oMfy1jrB2p5e8muAUXc4asGk2SsSgsERhzUjXNHZSmr2xMWfWXcnJvX9sav2H`](https://explorer.solana.com/tx/4dtLcdt9CdCdoimcgNLouG8Kh72oMfy1jrB2p5e8muAUXc4asGk2SsSgsERhzUjXNHZSmr2xMWfWXcnJvX9sav2H?cluster=devnet) | note 2 | -| deposit | [`4nRS8qTSW9h58kimVCX8D7YWHHw4uicBnCfZcPqqLdcHcUCxnhGbX7GTjRKuy9RLSeHbrALGp7Gu3RAgfRMaJkSX`](https://explorer.solana.com/tx/4nRS8qTSW9h58kimVCX8D7YWHHw4uicBnCfZcPqqLdcHcUCxnhGbX7GTjRKuy9RLSeHbrALGp7Gu3RAgfRMaJkSX?cluster=devnet) | note 3 | -| deposit | [`4F5nMtQTdEHiBb2yDcZhwMtscsdFbxEZXxJvAt3ZAxYvTLJsC2bgQbiiw8pCsMYizuFUivHgQcz3JXG75x5efYEs`](https://explorer.solana.com/tx/4F5nMtQTdEHiBb2yDcZhwMtscsdFbxEZXxJvAt3ZAxYvTLJsC2bgQbiiw8pCsMYizuFUivHgQcz3JXG75x5efYEs?cluster=devnet) | note 4 | -| submit_spend | [`48RWESn6bf5ipZi9uYhUo31XJw4RcqZyxT3vk3U8g2UZ8bceZihUmUJrZCJTt9e18Mi2ymfQW8j26Vvoo2fKgYZu`](https://explorer.solana.com/tx/48RWESn6bf5ipZi9uYhUo31XJw4RcqZyxT3vk3U8g2UZ8bceZihUmUJrZCJTt9e18Mi2ymfQW8j26Vvoo2fKgYZu?cluster=devnet) | note 0, relay-signed | -| submit_spend | [`4QFM6p4cniHTmATLJPuMR89DyYohFM5N3wrXYGJJpiAQ3xzNLgz8hCD5pN5BEoyysRYDc6FcQD2xbRZggrw2Nsmi`](https://explorer.solana.com/tx/4QFM6p4cniHTmATLJPuMR89DyYohFM5N3wrXYGJJpiAQ3xzNLgz8hCD5pN5BEoyysRYDc6FcQD2xbRZggrw2Nsmi?cluster=devnet) | note 1, relay-signed | -| submit_spend | [`FHHgBrh8ytutD58XtSTGUKBJpgqq5rV9krBgq9VheaBJQEpQcucZkU85mHX9KnuvEiazDA753Se2azDSPb4RmtZ`](https://explorer.solana.com/tx/FHHgBrh8ytutD58XtSTGUKBJpgqq5rV9krBgq9VheaBJQEpQcucZkU85mHX9KnuvEiazDA753Se2azDSPb4RmtZ?cluster=devnet) | note 2, relay-signed | -| submit_spend | [`4ZFy3xaCokZoqWYvEw3iwRYQQr6obpMqdKjUvtUqhaPZqRW3RFSzX3Eva7QdvJLpnpMRXhvw9cEB26NY9Tbh8o6P`](https://explorer.solana.com/tx/4ZFy3xaCokZoqWYvEw3iwRYQQr6obpMqdKjUvtUqhaPZqRW3RFSzX3Eva7QdvJLpnpMRXhvw9cEB26NY9Tbh8o6P?cluster=devnet) | note 3, relay-signed | -| settle_epoch | [`67SdKHQ2fHcJ8nommceaaJnnFaCL5mtKwtuyy2uTHB2Lz8aauyDtGDTQ8SJFC28LMM33jZQHjb3cm3x9BXRiPM8W`](https://explorer.solana.com/tx/67SdKHQ2fHcJ8nommceaaJnnFaCL5mtKwtuyy2uTHB2Lz8aauyDtGDTQ8SJFC28LMM33jZQHjb3cm3x9BXRiPM8W?cluster=devnet) | 4 spends | -| deposit | [`4fcgHaFewWfoy2QMqpGTChxuhfFg8fTeWts6sW45rR1ZhJMk9fR4X6AQnZXBxiZW3Si55VNpSzuxh8FtL1p8ENtt`](https://explorer.solana.com/tx/4fcgHaFewWfoy2QMqpGTChxuhfFg8fTeWts6sW45rR1ZhJMk9fR4X6AQnZXBxiZW3Si55VNpSzuxh8FtL1p8ENtt?cluster=devnet) | note 5 | +| init_pool | [`nbk64A6kCYWh3yJKvZYrqKzNjQfFHnMREDqnbBMZGPRiJMch4TZXVMd3LLYozVKSbPYoMsgtUvJbo1yrKShQFQh`](https://explorer.solana.com/tx/nbk64A6kCYWh3yJKvZYrqKzNjQfFHnMREDqnbBMZGPRiJMch4TZXVMd3LLYozVKSbPYoMsgtUvJbo1yrKShQFQh?cluster=devnet) | denomination 20000017, k_floor 4 | +| deposit | [`4rh44zFpUrdyPaoNaiLDoKddzszpKwUGM3PmGX7XSF2StbJUYkjzu5k9NpCXhY9NWFuN4Ww1jxpGuSs5m6bU9QFu`](https://explorer.solana.com/tx/4rh44zFpUrdyPaoNaiLDoKddzszpKwUGM3PmGX7XSF2StbJUYkjzu5k9NpCXhY9NWFuN4Ww1jxpGuSs5m6bU9QFu?cluster=devnet) | note 1 | +| deposit | [`3mogP7EotT5F6s6B9vrdWzDSxs2WEskDk6GSzWK8vw8N1hpaEmX1KJ4nBrEgzBaNUCkqcxNdkEnAx6Td54SgRNa4`](https://explorer.solana.com/tx/3mogP7EotT5F6s6B9vrdWzDSxs2WEskDk6GSzWK8vw8N1hpaEmX1KJ4nBrEgzBaNUCkqcxNdkEnAx6Td54SgRNa4?cluster=devnet) | note 2 | +| deposit | [`4rj33jkYepFCZfEpncBBjeNBp7DS1LA9priRMsdPWpSMBiJL218g9rMwAD8WUwnX1pJ5GuvQEsve6Q417poK3CCL`](https://explorer.solana.com/tx/4rj33jkYepFCZfEpncBBjeNBp7DS1LA9priRMsdPWpSMBiJL218g9rMwAD8WUwnX1pJ5GuvQEsve6Q417poK3CCL?cluster=devnet) | note 3 | +| deposit | [`3YUQ7upP47RjUujKPe1Tcgv4ods1W8mCsV7jpWcqmRSEaU5GgRiivxHX8pFJmmgSgJ9ZpTfNGH9RCdeXhhYGpL47`](https://explorer.solana.com/tx/3YUQ7upP47RjUujKPe1Tcgv4ods1W8mCsV7jpWcqmRSEaU5GgRiivxHX8pFJmmgSgJ9ZpTfNGH9RCdeXhhYGpL47?cluster=devnet) | note 4 | +| submit_spend | [`4xkzxNGCqUo3Qu2TqoG15e6ZDWR1H9edad3V88ox3oYtmAAEKbJ4uzP5qkg7cfJaHbA6uREiLpmQWFU4h8L12CpH`](https://explorer.solana.com/tx/4xkzxNGCqUo3Qu2TqoG15e6ZDWR1H9edad3V88ox3oYtmAAEKbJ4uzP5qkg7cfJaHbA6uREiLpmQWFU4h8L12CpH?cluster=devnet) | note 0, relay-signed | +| submit_spend | [`49H8PgT13fUC9gNXJWaJ7ui3hBqwngPunwvTH4wFicNGe8BdGGKmbivorApnSE8JKT3VBfhwFc2nY8XMJ5RczmdV`](https://explorer.solana.com/tx/49H8PgT13fUC9gNXJWaJ7ui3hBqwngPunwvTH4wFicNGe8BdGGKmbivorApnSE8JKT3VBfhwFc2nY8XMJ5RczmdV?cluster=devnet) | note 1, relay-signed | +| submit_spend | [`2c5AyyspfzfBsJ8mW2LYKuUE8zTDx5wxFxheWZxjVyU6Lc7LtqqBcsgT8vJvurMAoPQetpiELBnxVGbMRnvqqoCk`](https://explorer.solana.com/tx/2c5AyyspfzfBsJ8mW2LYKuUE8zTDx5wxFxheWZxjVyU6Lc7LtqqBcsgT8vJvurMAoPQetpiELBnxVGbMRnvqqoCk?cluster=devnet) | note 2, relay-signed | +| submit_spend | [`4129ftZy7XoZ5vZLMtCZgoXsfphBAQr5fNtKNNJaViVZMfmbUS9SN5F8owbqdYaGTMBZTNVx7e3hCeyKXHcbERou`](https://explorer.solana.com/tx/4129ftZy7XoZ5vZLMtCZgoXsfphBAQr5fNtKNNJaViVZMfmbUS9SN5F8owbqdYaGTMBZTNVx7e3hCeyKXHcbERou?cluster=devnet) | note 3, relay-signed, action: pool-signed CPI to SPL Memo | +| settle_epoch | [`3wPUYgdYdC9AXfL1XYXeHP9YMWMJeZvGhBpq91qWRH6ZWQc4fmJkykjgzAR2GdUrUFumswNHKLjyEdewHW14EJPj`](https://explorer.solana.com/tx/3wPUYgdYdC9AXfL1XYXeHP9YMWMJeZvGhBpq91qWRH6ZWQc4fmJkykjgzAR2GdUrUFumswNHKLjyEdewHW14EJPj?cluster=devnet) | 4 spends in one transaction, 1 of them a CPI the pool signed | +| deposit | [`3L3qxxDpNf9rHRqXhf62xRfrDEZQEDeY52vmAti6gn3dokUk2HnmrpXoiUPHS7RKt1Fnz8vPPBfcNRBHmtGjDV2w`](https://explorer.solana.com/tx/3L3qxxDpNf9rHRqXhf62xRfrDEZQEDeY52vmAti6gn3dokUk2HnmrpXoiUPHS7RKt1Fnz8vPPBfcNRBHmtGjDV2w?cluster=devnet) | note 5 | ## Vault accounting @@ -29,17 +29,29 @@ The accounting invariant is a statement about the vault's lamports, so here are | quantity | lamports | |---|---| -| denomination | 20000007 | +| denomination | 20000017 | | notes settled | 4 | | relay fee (taken out of the denomination, not added) | 200000 | -| vault before settlement | 80890908 | +| vault before settlement | 80890948 | | vault after settlement | 890880 | | rent-exempt minimum, 0 bytes | 890880 | -| **paid out** | **80000028** | -| **owed** (denomination × notes) | **80000028** | +| **paid out** | **80000068** | +| **owed** (denomination × notes) | **80000068** | Paid out equals owed, and the vault came to rest on its floor with a remainder of 0 lamports. The soak asserts both and fails the run otherwise, so this table cannot record a discrepancy and still exit successfully. +## The pool signed an action, and the callee said so + +The settlement above carried four spends, and one of them was not a transfer: the pool invoked SPL Memo as that member's **authority**, in the same transaction as the other three. That is the capability a stake delegation or a governance vote needs and a payment does not. + +A signature only proves the transaction landed. It says nothing about who signed the instruction the pool made *inside* it, so the evidence has to come from the callee. SPL Memo refuses any account handed to it that has not signed, and names the ones that did: + +``` +Program log: Signed by 3x8NUzCzQdbjEX2T7gapDAgg6nJU8Y3ZsbUFPnqfhyLV +``` + +That is the pool's vault, `3x8NUzCzQdbjEX2T7gapDAgg6nJU8Y3ZsbUFPnqfhyLV`, which has no private key — it signed through seeds only the program holds. The soak reads this line back from the cluster and fails the run if it is absent, so this section cannot appear without the callee having said it. + ## Rejections A negative case is only evidence if the program's own error code is what rejected it. A bare runtime failure proves nothing about this program. diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 2692ed5b..4da93857 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -59,6 +59,25 @@ pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> Pr } } +/// One validated spend, held until the whole batch has been read. +/// +/// Settlement cannot execute a batch in a single pass. A call carrying the +/// pool's vault as a signer is refused by the runtime if this program has +/// already moved the vault's lamports in the same instruction, and "the same +/// instruction" spans the whole batch — so those calls have to happen before +/// every payout, including payouts owed to other members. +struct Pending<'a> { + spend: AccountInfo<'a>, + beneficiary: AccountInfo<'a>, + relay: AccountInfo<'a>, + /// The callee's own account, for the two selectors that make a call. + target: Option>, + action_infos: Vec>, + selector: u64, + relay_fee: u64, + payout: u64, +} + /// The fields of a spend, grouped so the handler takes one argument. struct SpendRequest { proof_a: [u8; 64], @@ -527,7 +546,7 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro let crowd_satisfied = count as u32 >= k_floor; // Each spend brings its record, its beneficiary and its relay. - let mut settled = 0u64; + let mut pending: Vec = Vec::with_capacity(count as usize); for _ in 0..count { let spend_account = next_account_info(iter)?; let beneficiary = next_account_info(iter)?; @@ -574,15 +593,12 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro ) }; - match selector { + let (target, action_infos) = match selector { SELECTOR_TRANSFER => { if action_accounts != 0 { return Err(MirrorProgramError::MalformedInstruction.into()); } - // The vault is owned by this program, so lamports move by direct - // mutation rather than a system CPI: no signer seeds and no - // nested invoke on the hot path. - move_lamports(vault_account, beneficiary, payout)?; + (None, Vec::new()) } SELECTOR_INVOKE | SELECTOR_INVOKE_SIGNED => { // The target program's own account comes first, because a CPI @@ -590,43 +606,96 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro // list. Its key is checked against the record, so a settler // supplying a different program is refused before any value // moves rather than discovered by the runtime. - let target_info = next_account_info(iter)?; + let target_info = next_account_info(iter)?.clone(); // Then the action's own accounts. let mut action_infos = Vec::with_capacity(action_accounts as usize); for _ in 0..action_accounts { action_infos.push(next_account_info(iter)?.clone()); } + (Some(target_info), action_infos) + } + _ => return Err(MirrorProgramError::UnknownSelector.into()), + }; + + pending.push(Pending { + spend: spend_account.clone(), + beneficiary: beneficiary.clone(), + relay: relay.clone(), + target, + action_infos, + selector, + relay_fee, + payout, + }); + } + + // Pass one: every call the pool signs, before a single lamport in this + // instruction has moved. + // + // This is the constraint that forces two passes rather than one, and it is + // about the *batch* and not about the spend. A CPI carrying the vault is + // refused by the runtime if this program has already mutated the vault's + // lamports anywhere in the same instruction — including on behalf of a + // different member earlier in the batch. Settling one signed action after + // three transfers is the case that finds this, and it is the ordinary case: + // a crowd is mixed by definition, and a signed action that could only settle + // alone would have to wait out the timeout, which is precisely the + // synchronised batch it exists to join. + for p in &pending { + if p.selector == SELECTOR_INVOKE_SIGNED { + let target_info = p + .target + .as_ref() + .ok_or(MirrorProgramError::MalformedInstruction)?; + invoke_action( + program_id, + pool_account, + vault_account, + &p.spend, + &p.beneficiary, + target_info, + p.payout, + &p.action_infos, + true, + )?; + } + } + + // Pass two: the money, and the calls that need to be funded before they run. + for p in &pending { + match p.selector { + // The vault is owned by this program, so lamports move by direct + // mutation rather than a system CPI: no signer seeds and no nested + // invoke on the hot path. + SELECTOR_TRANSFER | SELECTOR_INVOKE_SIGNED => { + move_lamports(vault_account, &p.beneficiary, p.payout)?; + } + SELECTOR_INVOKE => { + let target_info = p + .target + .as_ref() + .ok_or(MirrorProgramError::MalformedInstruction)?; invoke_action( program_id, pool_account, vault_account, - spend_account, - beneficiary, + &p.spend, + &p.beneficiary, target_info, - payout, - &action_infos, - selector == SELECTOR_INVOKE_SIGNED, + p.payout, + &p.action_infos, + false, )?; } _ => return Err(MirrorProgramError::UnknownSelector.into()), } - - // The relay is paid after the action, never before. - // - // Under `SELECTOR_INVOKE_SIGNED` the vault is one of the callee's - // accounts, and any direct lamport mutation this program makes to it - // before that call makes the runtime reject the whole instruction as - // `UnbalancedInstruction`. Paying the relay here rather than above the - // match is what keeps the vault's balance untouched at the moment of - // the CPI. The other selectors do not care about the order, so there is - // one order rather than two. - if relay_fee > 0 { - move_lamports(vault_account, relay, relay_fee)?; + if p.relay_fee > 0 { + move_lamports(vault_account, &p.relay, p.relay_fee)?; } - settled += 1; } + let settled = pending.len() as u64; let mut data = pool_account.try_borrow_mut_data()?; let mut pool = Pool::load(&mut data)?; for _ in 0..settled { @@ -713,7 +782,8 @@ fn invoke_action<'a>( return Err(MirrorProgramError::MalformedInstruction.into()); } // Fund the action before invoking, so the target sees the value it is - // meant to act on. + // meant to act on. Under `SELECTOR_INVOKE_SIGNED` the caller pays after + // every signed call in the batch has run instead. move_lamports(vault_account, beneficiary, payout)?; } @@ -754,16 +824,7 @@ fn invoke_action<'a>( &ix, &infos, &[&[VAULT_SEED, pool_account.key.as_ref(), &[vault_bump]]], - )?; - - // The payout happens *after* the call on this path, which is the whole - // reason the vault could be handed to the callee as a signer. The - // beneficiary is paid either way before this instruction returns, so the - // ordering changes what the callee observes and nothing about what is owed. - if pool_signs { - move_lamports(vault_account, beneficiary, payout)?; - } - Ok(()) + ) } /// Moves lamports between two accounts this program owns or may credit. diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index 9ceafdd7..0647034a 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -1375,6 +1375,99 @@ fn the_pool_signs_an_action_as_its_own_authority() { assert_eq!(Spend::load(&mut data).unwrap().status(), STATUS_SETTLED); } +/// A mixed batch: plain transfers and a call the pool signs, settled together. +/// +/// This is the case a live cluster found and a single-spend test could not. The +/// runtime's objection is not to a signed call following a payout *for that +/// member* — it is to a signed call following any lamport this program moved in +/// the same instruction, which in a batch means the three members settled before +/// it. Settlement therefore runs every signed call first and pays everybody +/// afterwards. +/// +/// The shape matters beyond the bug. A crowd is mixed by definition, and a +/// signed action that could only settle by itself would have to wait out the +/// timeout instead of joining a batch — losing the shared timestamp that makes +/// the crowd worth standing in. +#[test] +fn a_signed_action_settles_inside_a_batch_of_plain_transfers() { + let (mut env, tree, notes, keys) = seeded_pool(8); + let memo: Pubkey = MEMO_PROGRAM.parse().unwrap(); + env.svm.add_program(memo, &memo_bytes()).unwrap(); + + // Three ordinary members first, so the vault has already moved by the time + // the signed call runs. + let mut batch = submit_batch(&mut env, &tree, ¬es, &keys, 3); + let mut targets: Vec> = vec![None, None, None]; + let mut action_accounts: Vec> = vec![Vec::new(), Vec::new(), Vec::new()]; + + let signer_beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let payload = b"one of four".as_slice(); + let proof = action_proof_sel( + &keys, + &tree, + ¬es, + 3, + &memo, + &signer_beneficiary, + 1, + payload, + INVOKE_SIGNED, + ); + let nullifier = proof.public_inputs[1]; + let ix = action_ix_sel( + &env, + &proof, + nullifier, + &memo, + &signer_beneficiary, + &relay.pubkey(), + 1, + payload, + INVOKE_SIGNED, + ); + env.send(ix, &relay).expect("submitting the signed action"); + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + batch.push((spend_pda, signer_beneficiary, relay.insecure_clone())); + targets.push(Some(memo)); + action_accounts.push(vec![AccountMeta::new(env.vault, false)]); + + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let vault_before = env.vault_lamports(); + let ix = settle_ix_full(&env, &batch, &settler.pubkey(), &targets, &action_accounts); + let msg = Message::new(&[ix], Some(&settler.pubkey())); + let tx = Transaction::new(&[&settler], msg, env.svm.latest_blockhash()); + let meta = env + .svm + .send_transaction(tx) + .unwrap_or_else(|e| panic!("mixed batch: {:?}\n{:#?}", e.err, e.meta.logs)); + + let vault = env.vault.to_string(); + assert!( + meta.logs + .iter() + .any(|l| l.contains("Signed by") && l.contains(&vault)), + "the pool did not sign inside a mixed batch: {:#?}", + meta.logs + ); + println!( + "mixed batch of 4 settled, one of them pool-signed, {} CU", + meta.compute_units_consumed + ); + + // Everyone in the batch is paid, whatever their action was. + for (_, beneficiary, _) in &batch { + assert_eq!( + env.svm.get_account(beneficiary).unwrap().lamports, + DENOMINATION - RELAY_FEE, + "a member of the mixed batch went unpaid" + ); + } + assert_eq!(vault_before - env.vault_lamports(), 4 * DENOMINATION); +} + /// The attack the signing selector invites: point the pool's own signature at /// the System Program and tell it to move the escrow somewhere else. /// From 8a291d72875047b03a593c0254c2c38cc8fbd633 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 17:48:34 -0300 Subject: [PATCH 56/83] docs: reconcile the claims the new selector changed The batch-level ordering constraint, the devnet evidence, and the negative-case count all moved with the feature, and three documents still described the state before it. THREAT_MODEL counted 'every negative case but one' as carrying our own error code; there are two now, and the second is the vault-drain attempt, refused by the System Program's ownership rule rather than by us. Saying so matters more than the count: it names what the safety actually rests on. README and ARCHITECTURE carried the old devnet accounting, 80,000,028 against a run that has since been replaced. 198 tests, make verify green. --- README.md | 10 ++++++++++ docs/THREAT_MODEL.md | 23 ++++++++++++++++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 0685fa56..deefa941 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,16 @@ orderings cannot hold at once, so the member chooses, and the selector is inside the action binding — a settler cannot obtain the pool's signature for a proof that did not ask for it. +The constraint turned out to be about the **batch**, not the spend, and a live +cluster is what found it. The runtime objects to any lamport this program moved +anywhere in the same instruction, so three transfers settled ahead of a signed +call are enough to break it — a case a single-spend test cannot reach. +Settlement runs every signed call first and pays everybody afterwards. +`a_signed_action_settles_inside_a_batch_of_plain_transfers` pins it, because a +signed action that could only settle alone would have to wait out the timeout +rather than join a batch, giving up the shared timestamp that is the reason to +stand in a crowd. + A separate test passes a **non-empty account list** through the CPI, which matters because SPL Memo requires every account handed to it to have signed. If the program dropped an account, mislabelled a signer flag, or miscounted, Memo diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 7d11fa18..de0ca536 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -133,11 +133,22 @@ so the member picks, and the selector is inside the action binding — a settler cannot obtain the pool's signature for a proof that did not ask for it, and is refused by name if it tries. +The constraint is a property of the **batch** and not of the spend, which a live +cluster established and the tests had missed. The runtime objects to any lamport +this program moved anywhere in the same instruction, so a signed call settled +behind three transfers fails even though the same call settled alone succeeds. +Settlement therefore runs every signed call before any payout in the batch. +`a_signed_action_settles_inside_a_batch_of_plain_transfers` is the regression +test, and the shape matters on its own: a signed action that could only settle +alone would have to wait out `SETTLE_TIMEOUT_SECONDS` instead of joining a crowd, +surrendering the shared timestamp that makes the crowd worth standing in. + `the_pool_signs_an_action_as_its_own_authority` asserts the capability against the real SPL Memo program, which refuses any account handed to it that has not signed and names its signers in its logs. The test reads that log for the vault's own pubkey, so the claim rests on a third-party program's behaviour -rather than on ours. +rather than on ours. `docs/PROOF.md` carries the same evidence from devnet, read +back off the cluster by the soak rather than asserted by it. ### The action's account list is chosen by the settler @@ -185,10 +196,12 @@ and it is the reason this limitation is measurable rather than merely admitted. ### Not audited No external review. The end-to-end suite runs against the compiled program on a -real SVM and every negative case but one carries this program's own error code — -the truncated-account-list case is caught by the runtime before the program can -rule on it — which is -evidence of behaviour and not a substitute for an audit. +real SVM, and every negative case but two carries this program's own error code. +The two exceptions are caught by the runtime before this program can rule on +them, and they are marked as such rather than counted as our checks: a truncated +account list, and the attempt to turn the pool's signature against its own vault, +which dies on the System Program's ownership rule. That is evidence of behaviour +and not a substitute for an audit. ## Deliberate non-goals From 328ec9b508e97abd4192a6d825c11ec8e389234f Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 18:11:37 -0300 Subject: [PATCH 57/83] feat: the pool delegates real stake on devnet, as a member's authority MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The signing selector had SPL Memo behind it, which proves the mechanism and nothing about the use. This is the use: a real stake account on devnet, delegated by the pool, settled in the same transaction as three plain transfers and a memo. Delegated Stake: 1.09771712 SOL, activating Delegated Vote Account: 2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv Stake Authority: CWxsJdxBLm3LC6dEnBco68a6T4QNEF31N3qQRy95wN3Q Withdraw Authority: H9DRVAD42eiQqmXeYrX5AWwoYRr4wie4MzvJDC6Wkxmn DelegateStake requires the staker authority to sign, and a member who signs it has appeared on chain and undone the point. So the pool signs. An observer gets the validator, the amount and the timing, and cannot say which of the five members asked for it. That is the entire thesis in one transaction, and it is the first artifact here where the anonymity claim is about a behaviour rather than about a payment. The stake authority is the vault. The withdraw authority is the operator, and the asymmetry is the threat model applied rather than repeated: the pool's signature is available to every member, so an authority the vault holds is one every member holds. Delegation survives that — the worst a member can do is re-delegate somebody's stake. Withdrawal does not. The member's escrow is inside the delegation rather than beside it. The stake account is the spend's beneficiary, so the payout lands there: 1.1 SOL from the operator plus 0.0198 from the member. The account list came from the CLI rather than from memory. DelegateStake takes six accounts with the authority last and the deprecated config account still in the middle; getting that order wrong is not a compile error and not a clear runtime one either — the stake program would read config as the authority and report a missing signature. Dumping an unsigned message cost nothing and removed the guess. Verified twice over, neither time by us. The soak reads the account back and refuses to publish unless it has reached the Stake variant, which an initialised account has not; and the solana CLI's own stake-account command agrees from outside the repository. 198 tests, make verify green. --- README.md | 40 ++++- crates/mirror-cli/src/soak.rs | 325 +++++++++++++++++++++++++++++++--- data/soak-notes-20000019.json | 1 + docs/ARCHITECTURE.md | 15 +- docs/PROOF.md | 53 +++--- docs/THREAT_MODEL.md | 7 + 6 files changed, 390 insertions(+), 51 deletions(-) create mode 100644 data/soak-notes-20000019.json diff --git a/README.md b/README.md index deefa941..9af16a72 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,9 @@ bolted on: pool. `the_pool_signs_an_action_as_its_own_authority` proves it at **30,827 CU** against real SPL Memo — a program that refuses any account handed to it that has not signed, and that names its signers in its logs. The test reads that log - for the vault's own key, so the claim rests on someone else's program. + for the vault's own key, so the claim rests on someone else's program. Devnet + carries the case this exists for: a **real stake delegation**, with the pool as + the staker authority — [see Deployment](#deployment). - **Moving lamports is the degenerate case.** Selector zero is a plain transfer, kept only because expressing "pay this account" should not require a target program. @@ -384,17 +386,37 @@ lifecycle ran there against a real validator — pool creation, deposits, spends each carrying a Groth16 proof verified by the deployed program's own syscall, and a settlement that closed the vault to its rent-exempt minimum to the lamport. -That settlement carried four spends and **one of them was not a transfer**: the -pool invoked SPL Memo as that member's authority, in the same transaction as the -other three. A signature only proves a transaction landed, so the evidence comes -from the callee — Memo names its signers, and it named the pool's vault, an -account with no private key. The soak reads that line back off the cluster and -fails the run if it is missing, so `docs/PROOF.md` cannot carry the claim -without the claim being true. +That settlement carried five spends, and **two of them were not transfers**. One +was a memo the pool signed. The other was a **real stake delegation**: + +``` +Delegated Stake: 1.09771712 SOL, activating +Delegated Vote Account: 2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv +Stake Authority: CWxsJdxBLm3LC6dEnBco68a6T4QNEF31N3qQRy95wN3Q ← the pool's vault +Withdraw Authority: H9DRVAD42eiQqmXeYrX5AWwoYRr4wie4MzvJDC6Wkxmn ← the operator +``` + +`DelegateStake` requires the staker authority to sign, and no member can be that +authority without appearing on chain and undoing the point. So the pool was, and +the pool signed. That is the whole design in one transaction: an observer sees +that a delegation happened, to which validator, for how much, and **cannot say +which of the five members asked for it**. + +The withdraw authority is deliberately *not* the pool, and that is the threat +model applied rather than restated: the pool's signature is available to every +member, so an authority the vault holds is one every member holds. Delegation +survives that — the worst a member can do is re-delegate somebody's stake. +Withdrawal does not. + +Evidence never rests on our own word. Memo names its signers and named the +vault; the stake account is read back after settlement and only reaches the +`Stake` variant by being delegated. The soak checks both against the cluster and +fails the run if either is absent, so `docs/PROOF.md` cannot carry these claims +without them being true. That file has every signature *and* the lamports, because "closed to the rent-exempt minimum" is the interesting part of that sentence and a list of -signatures does not show it: 80,000,068 owed against 80,000,068 paid out, and a +signatures does not show it: 100,000,095 owed against 100,000,095 paid out, and a vault resting on its floor with a remainder of zero. The soak asserts both and fails the run otherwise, so that table cannot record a discrepancy and still exit successfully. diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs index 88a30306..22ebab9f 100644 --- a/crates/mirror-cli/src/soak.rs +++ b/crates/mirror-cli/src/soak.rs @@ -26,9 +26,13 @@ use solana_transaction::Transaction; // pool. Changing it starts a clean one, which is how the evidence run gets to // record its own `init_pool` rather than reusing a pool an earlier run created // and leaving the creation step undocumented. -const DENOMINATION: u64 = 20_000_017; // 0.02 SOL +const DENOMINATION: u64 = 20_000_019; // 0.02 SOL const ENTRY_FEE: u64 = 0; const K_FLOOR: u32 = 4; +/// Members in the evidence batch. Above `K_FLOOR` so the crowd rule is satisfied +/// by the crowd rather than by the timeout, and large enough to hold three +/// plain transfers beside the two actions the pool signs. +const BATCH: u64 = 5; const RELAY_FEE: u64 = 200_000; /// The SPL Memo program, at the same address on every cluster. @@ -43,6 +47,33 @@ const MEMO_PROGRAM: &str = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; /// What the member asks the pool to say on their behalf. const MEMO_PAYLOAD: &[u8] = b"mirror-pool: signed by the pool, for a member"; +/// The native Stake program. +const STAKE_PROGRAM: &str = "Stake11111111111111111111111111111111111111"; +/// Still in `DelegateStake`'s account list, deprecated but not removed. +const STAKE_CONFIG: &str = "StakeConfig11111111111111111111111111111111"; +const SYSVAR_CLOCK: &str = "SysvarC1ock11111111111111111111111111111111"; +const SYSVAR_STAKE_HISTORY: &str = "SysvarStakeHistory1111111111111111111111111"; +const SYSVAR_RENT: &str = "SysvarRent111111111111111111111111111111111"; + +/// `StakeInstruction::DelegateStake`, a bare u32 discriminant. +const DELEGATE_STAKE: [u8; 4] = [2, 0, 0, 0]; + +/// A devnet validator with a large active stake, so the delegation has somewhere +/// real to go. +const VOTE_ACCOUNT: &str = "2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv"; + +/// `StakeStateV2` is 200 bytes whatever variant it holds. +const STAKE_ACCOUNT_LEN: usize = 200; + +/// What the operator puts into the stake account before the pool delegates it. +/// +/// Devnet's minimum delegation is 1 SOL and this pool's denomination is 0.02, so +/// the escrow of one member cannot reach the floor by itself — a pool whose +/// denomination cleared it would need five-plus SOL to fill a single batch. The +/// operator funds the account and the member's escrow is added to it; what the +/// *pool* supplies is the authority, which is the part being demonstrated. +const STAKE_FUNDING: u64 = 1_100_000_000; + /// One member's pending spend, as settlement needs to see it. pub struct Settlement { pub spend: Pubkey, @@ -51,6 +82,8 @@ pub struct Settlement { /// The CPI target, when this member's action is a call rather than a /// transfer. pub target: Option, + /// The accounts that action takes, in the order the callee expects them. + pub action_accounts: Vec, } /// One recorded step of the run. @@ -107,6 +140,8 @@ pub struct Soak { pub accounting: Option, /// The callee's own log line naming the pool's vault as a signer. pub signed_action: Option, + /// The stake account the pool delegated, and the validator it now backs. + pub delegation: Option<(Pubkey, Pubkey)>, } impl Soak { @@ -123,6 +158,7 @@ impl Soak { negatives: Vec::new(), accounting: None, signed_action: None, + delegation: None, } } @@ -428,6 +464,189 @@ impl Soak { Ok((spend_pda, nullifier)) } + /// Creates a stake account whose **staker** is the pool's vault. + /// + /// The withdraw authority is deliberately *not* the vault, and that is the + /// threat model applied rather than restated: the pool's signature is + /// available to every member, so an authority held by the vault is an + /// authority every member holds. Staking is safe to delegate that way + /// because the worst a member can do with it is delegate somebody else's + /// stake to a different validator. Withdrawal is not, so it stays with the + /// operator. + pub fn create_stake_account(&mut self) -> Result { + let stake = Keypair::new(); + let stake_program: Pubkey = STAKE_PROGRAM + .parse() + .map_err(|e| anyhow!("stake id: {e}"))?; + let rent_sysvar: Pubkey = SYSVAR_RENT.parse().map_err(|e| anyhow!("rent id: {e}"))?; + + let create = solana_system_interface::instruction::create_account( + &self.payer.pubkey(), + &stake.pubkey(), + STAKE_FUNDING, + STAKE_ACCOUNT_LEN as u64, + &stake_program, + ); + + // StakeInstruction::Initialize { Authorized, Lockup }, bincode: a u32 + // discriminant then the two authorities and a zero lockup. + let mut data = 0u32.to_le_bytes().to_vec(); + data.extend_from_slice(self.vault.as_ref()); // staker + data.extend_from_slice(&self.payer.pubkey().to_bytes()); // withdrawer + data.extend_from_slice(&0i64.to_le_bytes()); // lockup.unix_timestamp + data.extend_from_slice(&0u64.to_le_bytes()); // lockup.epoch + data.extend_from_slice(&[0u8; 32]); // lockup.custodian + let initialise = Instruction::new_with_bytes( + stake_program, + &data, + vec![ + AccountMeta::new(stake.pubkey(), false), + AccountMeta::new_readonly(rent_sysvar, false), + ], + ); + + let blockhash = self.client.latest_blockhash()?; + let message = + solana_message::Message::new(&[create, initialise], Some(&self.payer.pubkey())); + let payer = self.payer.insecure_clone(); + let tx = Transaction::new(&[&payer, &stake], message, blockhash); + let sig = self.client.send(&tx)?; + self.record( + "create stake account", + sig, + format!( + "{} lamports, staker = the pool's vault, withdrawer = the operator", + STAKE_FUNDING + ), + ); + Ok(stake.pubkey()) + } + + /// The action list `DelegateStake` expects, in the callee's own order. + /// + /// Taken from what the `solana` CLI builds rather than from memory: the + /// authority sits in the last slot, and the deprecated config account is + /// still in the list. Getting this order wrong is not a compile error and + /// not a clear runtime one either — the stake program would read the config + /// account as the authority and report a missing signature. + fn delegate_accounts(&self, stake: &Pubkey) -> Result> { + let parse = |s: &str| -> Result { s.parse().map_err(|e| anyhow!("{s}: {e}")) }; + Ok(vec![ + AccountMeta::new(*stake, false), + AccountMeta::new_readonly(parse(VOTE_ACCOUNT)?, false), + AccountMeta::new_readonly(parse(SYSVAR_CLOCK)?, false), + AccountMeta::new_readonly(parse(SYSVAR_STAKE_HISTORY)?, false), + AccountMeta::new_readonly(parse(STAKE_CONFIG)?, false), + AccountMeta::new_readonly(self.vault, false), + ]) + } + + /// Submits a spend whose action is a real stake delegation, authorised by + /// the pool. + #[allow(clippy::too_many_arguments)] + pub fn submit_stake_delegation( + &mut self, + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + stake: &Pubkey, + relay: &Keypair, + ) -> Result<(Pubkey, [u8; 32])> { + use ark_std::rand::SeedableRng; + let stake_program: Pubkey = STAKE_PROGRAM + .parse() + .map_err(|e| anyhow!("stake id: {e}"))?; + let merkle_proof = tree.proof(index as u64).map_err(|e| anyhow!("{e}"))?; + // The stake account is the beneficiary, so the member's escrow lands in + // the stake they authorised rather than beside it. + let binding = mirror_core::action_binding( + SELECTOR_INVOKE_SIGNED, + &stake_program.to_bytes(), + &stake.to_bytes(), + RELAY_FEE, + 6, + &DELEGATE_STAKE, + ); + let witness = Witness { + note: notes[index], + merkle_proof: &merkle_proof, + root: tree.root().map_err(|e| anyhow!("{e}"))?, + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([index as u8 + 1; 32]); + let proof = prove(keys, &witness, &mut rng).map_err(|e| anyhow!("{e}"))?; + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&self.program_id, &self.pool, &nullifier); + + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR_INVOKE_SIGNED, + target_program: stake_program.to_bytes(), + beneficiary: stake.to_bytes(), + relay_fee: RELAY_FEE, + action_accounts: 6, + payload: DELEGATE_STAKE.to_vec(), + } + .pack(), + vec![ + AccountMeta::new(relay.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let sig = self.send(ix, &[relay])?; + self.record( + "submit_spend", + sig, + format!("note {index}, relay-signed, action: pool-signed stake delegation"), + ); + Ok((spend_pda, nullifier)) + } + + /// Reads the stake account back and reports which validator it now backs. + /// + /// `StakeStateV2` is a bincode enum: a u32 discriminant, then `Meta` (8 bytes + /// of rent reserve, two 32-byte authorities, a 48-byte lockup), then + /// `Delegation`, whose first field is the vote account. Variant 2 is `Stake`, + /// which an account only reaches by being delegated — an initialised but + /// undelegated account is variant 1, so the discriminant alone distinguishes + /// "the instruction landed" from "the delegation took". + pub fn confirm_delegated(&mut self, stake: &Pubkey) -> Result<()> { + let data = self + .client + .account_data(stake)? + .ok_or_else(|| anyhow!("the stake account {stake} vanished"))?; + if data.len() < 156 { + return Err(anyhow!( + "the stake account is {} bytes, too short to be a stake state", + data.len() + )); + } + let discriminant = u32::from_le_bytes([data[0], data[1], data[2], data[3]]); + if discriminant != 2 { + return Err(anyhow!( + "the stake account is in state {discriminant}, not Stake(2): the \ + delegation did not take" + )); + } + let voter = Pubkey::new_from_array( + data[124..156] + .try_into() + .map_err(|_| anyhow!("reading the vote account"))?, + ); + println!(" stake account delegated to {voter}"); + self.delegation = Some((*stake, voter)); + Ok(()) + } + /// Submits a spend expected to fail, recording the program's error code. #[allow(clippy::too_many_arguments)] pub fn expect_rejection( @@ -489,10 +708,11 @@ impl Soak { if let Some(target) = entry.target { actions += 1; metas.push(AccountMeta::new_readonly(target, false)); - // The action's single account. It reaches the callee as a - // *signer* — the program sets that flag itself, from seeds no - // settler holds, which is why this meta says otherwise. - metas.push(AccountMeta::new(self.vault, false)); + // The action's own accounts, in the callee's order. Where one of + // them is the vault it reaches the callee as a *signer* — the + // program sets that flag itself, from seeds no settler holds, + // which is why the meta here says otherwise. + metas.extend(entry.action_accounts.iter().cloned()); } } let ix = Instruction::new_with_bytes( @@ -640,12 +860,12 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re // starts empty and the old notes are still in the file. let ledger_path = format!("data/soak-notes-{DENOMINATION}.json"); let ledger = std::path::Path::new(&ledger_path); - let (tree, notes) = soak.deposit(K_FLOOR as u64, ledger)?; + let (tree, notes) = soak.deposit(BATCH, ledger)?; // Spend the notes this run deposited, not the earliest ones. A rerun // against a pool that already holds spent notes would otherwise replay a // burnt nullifier and stop. - let first_fresh = notes.len() - K_FLOOR as usize; + let first_fresh = notes.len() - BATCH as usize; // One relay and one beneficiary per member: no member key appears anywhere. // @@ -655,16 +875,45 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re // timestamp, one signer and a batch of actions, with nothing separating the // member who staked from the members who merely moved value. let memo: Pubkey = MEMO_PROGRAM.parse().map_err(|e| anyhow!("memo id: {e}"))?; - let last = first_fresh + K_FLOOR as usize - 1; + let stake_program: Pubkey = STAKE_PROGRAM + .parse() + .map_err(|e| anyhow!("stake id: {e}"))?; + + // The stake account exists before any member asks for anything, and who + // created it is public. What the pool supplies is the authority to delegate + // it, and which member asked for that is what the anonymity set hides. + let stake = soak.create_stake_account()?; + + let memo_note = first_fresh + BATCH as usize - 2; + let stake_note = first_fresh + BATCH as usize - 1; let mut batch = Vec::new(); - for i in first_fresh..first_fresh + K_FLOOR as usize { - let beneficiary = Keypair::new().pubkey(); + for i in first_fresh..first_fresh + BATCH as usize { let relay = payer.insecure_clone(); - let (spend, target) = if i == last { + let entry = if i == stake_note { + let (spend, _) = + soak.submit_stake_delegation(&keys, &tree, ¬es, i, &stake, &relay)?; + Settlement { + spend, + // The stake account is the beneficiary, so this member's escrow + // ends up inside the stake they authorised. + beneficiary: stake, + relay: relay.pubkey(), + target: Some(stake_program), + action_accounts: soak.delegate_accounts(&stake)?, + } + } else if i == memo_note { + let beneficiary = Keypair::new().pubkey(); let (spend, _) = soak.submit_signed_action(&keys, &tree, ¬es, i, &beneficiary, &relay, &memo)?; - (spend, Some(memo)) + Settlement { + spend, + beneficiary, + relay: relay.pubkey(), + target: Some(memo), + action_accounts: vec![AccountMeta::new(soak.vault(), false)], + } } else { + let beneficiary = Keypair::new().pubkey(); let (spend, _) = soak.submit_spend( &keys, &tree, @@ -674,26 +923,28 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re &relay, "submit_spend", )?; - (spend, None) + Settlement { + spend, + beneficiary, + relay: relay.pubkey(), + target: None, + action_accounts: Vec::new(), + } }; - batch.push(Settlement { - spend, - beneficiary, - relay: relay.pubkey(), - target, - }); + batch.push(entry); } let vault_before = soak.balance(&soak.vault())?; let settle_sig = soak.settle(&batch)?; soak.confirm_pool_signed(&settle_sig)?; + soak.confirm_delegated(&stake)?; let vault_after = soak.balance(&soak.vault())?; // The vault carries no data, so its floor is the rent-exempt minimum for a // zero-byte account — asked of the cluster rather than assumed. let vault_floor = soak.rent_exempt_minimum(0)?; let accounting = Accounting { denomination: DENOMINATION, - notes_settled: K_FLOOR as u64, + notes_settled: BATCH, relay_fee: RELAY_FEE, vault_before_settle: vault_before, vault_after_settle: vault_after, @@ -890,6 +1141,40 @@ fn write_proof(soak: &Soak, url: &str, out: &std::path::Path) -> Result<()> { )); } + if let Some((stake, voter)) = &soak.delegation { + md.push_str("\n## The pool delegated stake, as a member's authority\n\n"); + md.push_str(&format!( + "The same settlement carried a second signed action, and this one is the case \ + the design exists for: a **real stake delegation**. Stake account \ + [`{stake}`](https://explorer.solana.com/address/{stake}?cluster=devnet) is now \ + delegated to validator \ + [`{voter}`](https://explorer.solana.com/address/{voter}?cluster=devnet).\n\n" + )); + md.push_str( + "`DelegateStake` requires the **staker authority** to sign. No member can be that \ + authority without appearing on chain and undoing the point, so the pool is, and \ + the pool signed. The account state is read back after settlement: a stake \ + account only reaches the `Stake` variant by being delegated — an initialised but \ + undelegated one is a different variant — so the check distinguishes \"the \ + instruction landed\" from \"the delegation took\", and the validator's key is \ + read out of the account rather than assumed from what was requested.\n\n", + ); + md.push_str( + "**The withdraw authority is deliberately not the pool.** The pool's signature is \ + available to every member, so an authority the vault holds is an authority every \ + member holds. Delegation is safe on those terms — the worst a member can do is \ + re-delegate to another validator. Withdrawal is not, and it stays with the \ + operator. That is `docs/THREAT_MODEL.md` applied rather than repeated.\n\n", + ); + md.push_str(&format!( + "What is hidden here is precisely one thing: **which member asked**. The stake \ + account, the validator, the amount and the timing are all public, and the \ + anonymity set is the {BATCH} members who settled together. What an observer \ + cannot recover is which of them authorised this delegation rather than one of \ + the plain transfers beside it.\n" + )); + } + md.push_str("\n## Rejections\n\n"); md.push_str( "A negative case is only evidence if the program's own error code is what \ diff --git a/data/soak-notes-20000019.json b/data/soak-notes-20000019.json new file mode 100644 index 00000000..ea0a659c --- /dev/null +++ b/data/soak-notes-20000019.json @@ -0,0 +1 @@ +[[1785105260299716,14136206147696],[1785105260299723,14136206147709],[1785105260299730,14136206147722],[1785105260299737,14136206147735],[1785105260299744,14136206147748],[1785105287299797,14136206361509]] \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index ccd20672..e8b235bd 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -79,8 +79,8 @@ credited to it, and pool creation refuses a nonzero entry fee, so there is no second category of lamports anywhere that could be mistaken for backing for an unspent note. -`docs/PROOF.md` carries the devnet numbers: four notes settled, 80,000,068 -lamports owed and 80,000,068 paid, and a vault that came to rest on its +`docs/PROOF.md` carries the devnet numbers: five notes settled, 100,000,095 +lamports owed and 100,000,095 paid, and a vault that came to rest on its rent-exempt floor with a remainder of zero. The soak asserts that rather than printing it, so a run that disagreed would fail instead of publishing. @@ -181,6 +181,17 @@ That constraint was measured, not reasoned about: the earlier design refused the vault outright and documented the refusal as a property of the runtime. It is a property of the *ordering*. +It is also a property of the **batch**, which only a live cluster showed. The +runtime objects to any lamport this program moved anywhere in the same +instruction, so a signed call settled behind other members' payouts fails where +the same call alone succeeds. Settlement runs every signed call first and pays +afterwards — which it must, because a signed action that could only settle alone +would have to wait out the timeout instead of joining a crowd. + +`docs/PROOF.md` carries the case the selector exists for: a real stake +delegation on devnet, `DelegateStake` signed by the vault as staker authority, +settled in the same transaction as three plain transfers and a memo. + **`settle_epoch`** — executes a batch in one transaction so every payout shares a timestamp and an ordering. diff --git a/docs/PROOF.md b/docs/PROOF.md index f450b4e5..d1de46f2 100644 --- a/docs/PROOF.md +++ b/docs/PROOF.md @@ -4,24 +4,27 @@ Every line here is a transaction that landed. Nothing is simulated: the proof is - cluster: `devnet` - program: `8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa` -- pool: `AM7MrJGDWj83poTTRK4gVpCuGgybUKzNvZRW41x3yCfJ` -- vault: `3x8NUzCzQdbjEX2T7gapDAgg6nJU8Y3ZsbUFPnqfhyLV` +- pool: `5R5mrXeginkRYwv5NmXcSVVLpUW2obuc7YwdEBt31x5n` +- vault: `CWxsJdxBLm3LC6dEnBco68a6T4QNEF31N3qQRy95wN3Q` ## Flows | step | signature | note | |---|---|---| -| init_pool | [`nbk64A6kCYWh3yJKvZYrqKzNjQfFHnMREDqnbBMZGPRiJMch4TZXVMd3LLYozVKSbPYoMsgtUvJbo1yrKShQFQh`](https://explorer.solana.com/tx/nbk64A6kCYWh3yJKvZYrqKzNjQfFHnMREDqnbBMZGPRiJMch4TZXVMd3LLYozVKSbPYoMsgtUvJbo1yrKShQFQh?cluster=devnet) | denomination 20000017, k_floor 4 | -| deposit | [`4rh44zFpUrdyPaoNaiLDoKddzszpKwUGM3PmGX7XSF2StbJUYkjzu5k9NpCXhY9NWFuN4Ww1jxpGuSs5m6bU9QFu`](https://explorer.solana.com/tx/4rh44zFpUrdyPaoNaiLDoKddzszpKwUGM3PmGX7XSF2StbJUYkjzu5k9NpCXhY9NWFuN4Ww1jxpGuSs5m6bU9QFu?cluster=devnet) | note 1 | -| deposit | [`3mogP7EotT5F6s6B9vrdWzDSxs2WEskDk6GSzWK8vw8N1hpaEmX1KJ4nBrEgzBaNUCkqcxNdkEnAx6Td54SgRNa4`](https://explorer.solana.com/tx/3mogP7EotT5F6s6B9vrdWzDSxs2WEskDk6GSzWK8vw8N1hpaEmX1KJ4nBrEgzBaNUCkqcxNdkEnAx6Td54SgRNa4?cluster=devnet) | note 2 | -| deposit | [`4rj33jkYepFCZfEpncBBjeNBp7DS1LA9priRMsdPWpSMBiJL218g9rMwAD8WUwnX1pJ5GuvQEsve6Q417poK3CCL`](https://explorer.solana.com/tx/4rj33jkYepFCZfEpncBBjeNBp7DS1LA9priRMsdPWpSMBiJL218g9rMwAD8WUwnX1pJ5GuvQEsve6Q417poK3CCL?cluster=devnet) | note 3 | -| deposit | [`3YUQ7upP47RjUujKPe1Tcgv4ods1W8mCsV7jpWcqmRSEaU5GgRiivxHX8pFJmmgSgJ9ZpTfNGH9RCdeXhhYGpL47`](https://explorer.solana.com/tx/3YUQ7upP47RjUujKPe1Tcgv4ods1W8mCsV7jpWcqmRSEaU5GgRiivxHX8pFJmmgSgJ9ZpTfNGH9RCdeXhhYGpL47?cluster=devnet) | note 4 | -| submit_spend | [`4xkzxNGCqUo3Qu2TqoG15e6ZDWR1H9edad3V88ox3oYtmAAEKbJ4uzP5qkg7cfJaHbA6uREiLpmQWFU4h8L12CpH`](https://explorer.solana.com/tx/4xkzxNGCqUo3Qu2TqoG15e6ZDWR1H9edad3V88ox3oYtmAAEKbJ4uzP5qkg7cfJaHbA6uREiLpmQWFU4h8L12CpH?cluster=devnet) | note 0, relay-signed | -| submit_spend | [`49H8PgT13fUC9gNXJWaJ7ui3hBqwngPunwvTH4wFicNGe8BdGGKmbivorApnSE8JKT3VBfhwFc2nY8XMJ5RczmdV`](https://explorer.solana.com/tx/49H8PgT13fUC9gNXJWaJ7ui3hBqwngPunwvTH4wFicNGe8BdGGKmbivorApnSE8JKT3VBfhwFc2nY8XMJ5RczmdV?cluster=devnet) | note 1, relay-signed | -| submit_spend | [`2c5AyyspfzfBsJ8mW2LYKuUE8zTDx5wxFxheWZxjVyU6Lc7LtqqBcsgT8vJvurMAoPQetpiELBnxVGbMRnvqqoCk`](https://explorer.solana.com/tx/2c5AyyspfzfBsJ8mW2LYKuUE8zTDx5wxFxheWZxjVyU6Lc7LtqqBcsgT8vJvurMAoPQetpiELBnxVGbMRnvqqoCk?cluster=devnet) | note 2, relay-signed | -| submit_spend | [`4129ftZy7XoZ5vZLMtCZgoXsfphBAQr5fNtKNNJaViVZMfmbUS9SN5F8owbqdYaGTMBZTNVx7e3hCeyKXHcbERou`](https://explorer.solana.com/tx/4129ftZy7XoZ5vZLMtCZgoXsfphBAQr5fNtKNNJaViVZMfmbUS9SN5F8owbqdYaGTMBZTNVx7e3hCeyKXHcbERou?cluster=devnet) | note 3, relay-signed, action: pool-signed CPI to SPL Memo | -| settle_epoch | [`3wPUYgdYdC9AXfL1XYXeHP9YMWMJeZvGhBpq91qWRH6ZWQc4fmJkykjgzAR2GdUrUFumswNHKLjyEdewHW14EJPj`](https://explorer.solana.com/tx/3wPUYgdYdC9AXfL1XYXeHP9YMWMJeZvGhBpq91qWRH6ZWQc4fmJkykjgzAR2GdUrUFumswNHKLjyEdewHW14EJPj?cluster=devnet) | 4 spends in one transaction, 1 of them a CPI the pool signed | -| deposit | [`3L3qxxDpNf9rHRqXhf62xRfrDEZQEDeY52vmAti6gn3dokUk2HnmrpXoiUPHS7RKt1Fnz8vPPBfcNRBHmtGjDV2w`](https://explorer.solana.com/tx/3L3qxxDpNf9rHRqXhf62xRfrDEZQEDeY52vmAti6gn3dokUk2HnmrpXoiUPHS7RKt1Fnz8vPPBfcNRBHmtGjDV2w?cluster=devnet) | note 5 | +| init_pool | [`5x5wbVjg1g43KMCZQ8weY5eEjxfLizdjAiSvHVX7kA3bc1yRkKm4pAwWquzoCwwWzkxxaiTfAGoXyT2qi5n129hD`](https://explorer.solana.com/tx/5x5wbVjg1g43KMCZQ8weY5eEjxfLizdjAiSvHVX7kA3bc1yRkKm4pAwWquzoCwwWzkxxaiTfAGoXyT2qi5n129hD?cluster=devnet) | denomination 20000019, k_floor 4 | +| deposit | [`5NWeHxd2QLKskpPTPQhJJk5vuav8VojN1vw8PKwz8F3P2xXfjxfaAnScM57SqVRXv2HTNNsKpMdbZJCFVt63ppmZ`](https://explorer.solana.com/tx/5NWeHxd2QLKskpPTPQhJJk5vuav8VojN1vw8PKwz8F3P2xXfjxfaAnScM57SqVRXv2HTNNsKpMdbZJCFVt63ppmZ?cluster=devnet) | note 1 | +| deposit | [`2vNqo1GEEmRBpPCUUh8kiXuDUMz5ZkKNmJHKJfGPaHdW8jgDS9f3g3dTNUJsdEXtoRUXmSGCH8G7XURYqus3EBho`](https://explorer.solana.com/tx/2vNqo1GEEmRBpPCUUh8kiXuDUMz5ZkKNmJHKJfGPaHdW8jgDS9f3g3dTNUJsdEXtoRUXmSGCH8G7XURYqus3EBho?cluster=devnet) | note 2 | +| deposit | [`2KF8noWF6B4FCwDvrfGMwZkZoR7TR8ebDXwmAw4wCdFDxFQA9A5z2gsFjDDJm8YsC2pX7AQvsRPxjcLGCCa54wSg`](https://explorer.solana.com/tx/2KF8noWF6B4FCwDvrfGMwZkZoR7TR8ebDXwmAw4wCdFDxFQA9A5z2gsFjDDJm8YsC2pX7AQvsRPxjcLGCCa54wSg?cluster=devnet) | note 3 | +| deposit | [`2p8CVgPFQ1n36HTJAqebTNeRVxX958cKxnD1Xce3UhExd35DY7XLFDCMDT4mtKUsMKXStmaTuyDyiK93ZPqtQxqj`](https://explorer.solana.com/tx/2p8CVgPFQ1n36HTJAqebTNeRVxX958cKxnD1Xce3UhExd35DY7XLFDCMDT4mtKUsMKXStmaTuyDyiK93ZPqtQxqj?cluster=devnet) | note 4 | +| deposit | [`5udQNeabeCtLQ6KP3Kb4H1bmbiBfvLKfWsJ3Bg7oUivF4Kjqcn2Z3e8fh8mwYWE7PdcgLA3hJts8zyLM9xkgUd8K`](https://explorer.solana.com/tx/5udQNeabeCtLQ6KP3Kb4H1bmbiBfvLKfWsJ3Bg7oUivF4Kjqcn2Z3e8fh8mwYWE7PdcgLA3hJts8zyLM9xkgUd8K?cluster=devnet) | note 5 | +| create stake account | [`JWDRNZEWW78JRty7ARo8rJ3e4NtwKb4tdGkqBHzBSH52ruM1AA1oPQ31KBvhJTQjh4RXLxRFpVnmGhQfZR8q67E`](https://explorer.solana.com/tx/JWDRNZEWW78JRty7ARo8rJ3e4NtwKb4tdGkqBHzBSH52ruM1AA1oPQ31KBvhJTQjh4RXLxRFpVnmGhQfZR8q67E?cluster=devnet) | 1100000000 lamports, staker = the pool's vault, withdrawer = the operator | +| submit_spend | [`Ci9zMYy96rPowdJCWne1MVVb2p7jhcro3paKt6wJrvCsHZqziNKGCiiQpjV9Th9y7YLmnxRJcdLsJkviz7ztGXM`](https://explorer.solana.com/tx/Ci9zMYy96rPowdJCWne1MVVb2p7jhcro3paKt6wJrvCsHZqziNKGCiiQpjV9Th9y7YLmnxRJcdLsJkviz7ztGXM?cluster=devnet) | note 0, relay-signed | +| submit_spend | [`4nDV7VmAZHnLEEBNm2hgAX8yf4A59KL7NvMsnVpBevGtFKcadHGKPYRcPLK5XH4n29wTP8HUciCgYLKBUEqmYgYg`](https://explorer.solana.com/tx/4nDV7VmAZHnLEEBNm2hgAX8yf4A59KL7NvMsnVpBevGtFKcadHGKPYRcPLK5XH4n29wTP8HUciCgYLKBUEqmYgYg?cluster=devnet) | note 1, relay-signed | +| submit_spend | [`5uah1udmiCW1VHNiUi9SwqEwLYSMEP49WrxWzE178wpMHD1PFTjHiX8RuphKBtohQnVaThKJeEXkHtyxsPzT43C8`](https://explorer.solana.com/tx/5uah1udmiCW1VHNiUi9SwqEwLYSMEP49WrxWzE178wpMHD1PFTjHiX8RuphKBtohQnVaThKJeEXkHtyxsPzT43C8?cluster=devnet) | note 2, relay-signed | +| submit_spend | [`mPaXDDAQmMjCJmowSRwsJK2y4S38Y2NF9FAH4EniZJGcVtjomifSgJX5gaJqxjpS7XdKtvvQfQ6CJB3hX9PXp4q`](https://explorer.solana.com/tx/mPaXDDAQmMjCJmowSRwsJK2y4S38Y2NF9FAH4EniZJGcVtjomifSgJX5gaJqxjpS7XdKtvvQfQ6CJB3hX9PXp4q?cluster=devnet) | note 3, relay-signed, action: pool-signed CPI to SPL Memo | +| submit_spend | [`53ZdkNqVp7APhPKRQKVnUiKsCxKAKbaUrEK54EFSWuC3g99JzLqryzES6FM2TubdkgdY9sPVP9trSmcNVeHy9hBW`](https://explorer.solana.com/tx/53ZdkNqVp7APhPKRQKVnUiKsCxKAKbaUrEK54EFSWuC3g99JzLqryzES6FM2TubdkgdY9sPVP9trSmcNVeHy9hBW?cluster=devnet) | note 4, relay-signed, action: pool-signed stake delegation | +| settle_epoch | [`5VpoicNHu6m7YUvUSX7x2sztRzc751rmMRMg5SBR6qLKDxv5tFYG5pJmqCrDJajpD2sR8dCBd9BXqgooLw86iNqN`](https://explorer.solana.com/tx/5VpoicNHu6m7YUvUSX7x2sztRzc751rmMRMg5SBR6qLKDxv5tFYG5pJmqCrDJajpD2sR8dCBd9BXqgooLw86iNqN?cluster=devnet) | 5 spends in one transaction, 2 of them a CPI the pool signed | +| deposit | [`44bFc7PBkwMAMamkKVfMPWhiBHm7cRbCcJwfZed78Q2z2SyVrHzPXswWzoDAjf9wufGveEvTougb4f1E4DT7yWzr`](https://explorer.solana.com/tx/44bFc7PBkwMAMamkKVfMPWhiBHm7cRbCcJwfZed78Q2z2SyVrHzPXswWzoDAjf9wufGveEvTougb4f1E4DT7yWzr?cluster=devnet) | note 6 | ## Vault accounting @@ -29,14 +32,14 @@ The accounting invariant is a statement about the vault's lamports, so here are | quantity | lamports | |---|---| -| denomination | 20000017 | -| notes settled | 4 | +| denomination | 20000019 | +| notes settled | 5 | | relay fee (taken out of the denomination, not added) | 200000 | -| vault before settlement | 80890948 | +| vault before settlement | 100890975 | | vault after settlement | 890880 | | rent-exempt minimum, 0 bytes | 890880 | -| **paid out** | **80000068** | -| **owed** (denomination × notes) | **80000068** | +| **paid out** | **100000095** | +| **owed** (denomination × notes) | **100000095** | Paid out equals owed, and the vault came to rest on its floor with a remainder of 0 lamports. The soak asserts both and fails the run otherwise, so this table cannot record a discrepancy and still exit successfully. @@ -47,10 +50,20 @@ The settlement above carried four spends, and one of them was not a transfer: th A signature only proves the transaction landed. It says nothing about who signed the instruction the pool made *inside* it, so the evidence has to come from the callee. SPL Memo refuses any account handed to it that has not signed, and names the ones that did: ``` -Program log: Signed by 3x8NUzCzQdbjEX2T7gapDAgg6nJU8Y3ZsbUFPnqfhyLV +Program log: Signed by CWxsJdxBLm3LC6dEnBco68a6T4QNEF31N3qQRy95wN3Q ``` -That is the pool's vault, `3x8NUzCzQdbjEX2T7gapDAgg6nJU8Y3ZsbUFPnqfhyLV`, which has no private key — it signed through seeds only the program holds. The soak reads this line back from the cluster and fails the run if it is absent, so this section cannot appear without the callee having said it. +That is the pool's vault, `CWxsJdxBLm3LC6dEnBco68a6T4QNEF31N3qQRy95wN3Q`, which has no private key — it signed through seeds only the program holds. The soak reads this line back from the cluster and fails the run if it is absent, so this section cannot appear without the callee having said it. + +## The pool delegated stake, as a member's authority + +The same settlement carried a second signed action, and this one is the case the design exists for: a **real stake delegation**. Stake account [`53hDajhaYAvLZnazS4e86huMN8Uba8X6nAGNpAE9dL7R`](https://explorer.solana.com/address/53hDajhaYAvLZnazS4e86huMN8Uba8X6nAGNpAE9dL7R?cluster=devnet) is now delegated to validator [`2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv`](https://explorer.solana.com/address/2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv?cluster=devnet). + +`DelegateStake` requires the **staker authority** to sign. No member can be that authority without appearing on chain and undoing the point, so the pool is, and the pool signed. The account state is read back after settlement: a stake account only reaches the `Stake` variant by being delegated — an initialised but undelegated one is a different variant — so the check distinguishes "the instruction landed" from "the delegation took", and the validator's key is read out of the account rather than assumed from what was requested. + +**The withdraw authority is deliberately not the pool.** The pool's signature is available to every member, so an authority the vault holds is an authority every member holds. Delegation is safe on those terms — the worst a member can do is re-delegate to another validator. Withdrawal is not, and it stays with the operator. That is `docs/THREAT_MODEL.md` applied rather than repeated. + +What is hidden here is precisely one thing: **which member asked**. The stake account, the validator, the amount and the timing are all public, and the anonymity set is the 5 members who settled together. What an observer cannot recover is which of them authorised this delegation rather than one of the plain transfers beside it. ## Rejections diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index de0ca536..9f0ca519 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -150,6 +150,13 @@ vault's own pubkey, so the claim rests on a third-party program's behaviour rather than on ours. `docs/PROOF.md` carries the same evidence from devnet, read back off the cluster by the soak rather than asserted by it. +The devnet run also applies this section rather than restating it. It delegates +a real stake account with the vault as **staker** authority and the operator as +**withdraw** authority, because delegation survives being available to every +member — the worst any of them can do is re-delegate to another validator — and +withdrawal does not. An integrator who gives the vault a withdraw authority has +given it to the whole pool. + ### The action's account list is chosen by the settler The proof binds the selector, the target program, the beneficiary, the relay From 32bc2a0802b09a028fe777fcb396aa5d4c78f0ef Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 18:29:28 -0300 Subject: [PATCH 58/83] docs: the last comparative claim, in the file that predates the sweep MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PLAN.md still described the measurement method as targeting weaknesses found in other people's work. Same objection as before: a reader cannot audit work they do not have, and the three failure modes stand on their own — selection that drops whatever is expensive to trace, a sample of seconds presented as a population, a class key at raw-address resolution. Stated as failure modes, they are checkable against our own method. --- docs/PLAN.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/PLAN.md b/docs/PLAN.md index 6100aa3c..1bf7f038 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -152,12 +152,13 @@ without a crowd gate is not an incentive, it is a refund with extra steps. A separate crate collects real mainnet funding data and reports effective anonymity at several label resolutions, with the raw sample committed so results -reproduce without RPC access. The methodology is `docs/PROVENANCE_METHOD.md`; it -targets -the specific weaknesses found in the two published attempts: selection bias that -inflates the untraceable bucket, a sample spanning four seconds of chain time -presented as a population, and a class key defined at raw-address resolution so -that two members funded from the same exchange land in different classes. +reproduce without RPC access. The methodology is `docs/PROVENANCE_METHOD.md`, and +it is built around three failure modes that make a provenance number read better +than it is: selection that drops whatever is expensive to trace, which inflates +the untraceable bucket in the flattering direction; a sample spanning seconds of +chain time presented as a population; and a class key at raw-address resolution, +which splits two members funded by the same exchange into different classes and +so reports a larger anonymity set than exists. ## Schedule From 7734b448eb1eaf9cd94706a2322637c74bb96c1c Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 18:43:33 -0300 Subject: [PATCH 59/83] docs: name the pool the headline number is about MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The README said 'measured on a live pool's depositors' and left it there. The pool is Privacy Cash, an unrelated live mixer, and a reader who assumed the number described mirror-pool would have been wrong in the flattering direction. The reason is good — mirror-pool has no depositors, so measuring it would be measuring nothing — but the reason belongs in the README rather than only in the measurement log, and the fact that this is a tool pointed at somebody else's protocol is a feature to state plainly, not a detail to leave implicit. --- README.md | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 9af16a72..4dbe33aa 100644 --- a/README.md +++ b/README.md @@ -43,13 +43,19 @@ So this submission claims exactly two things: action's on-chain funding trace leads to the pool and is identical for every member. 2. **The membership side is measured**, from real mainnet data, with the method - and its limits published beside the number. Measured on a live pool's - depositors: **`ρ = 0.0955`**, inside an unresolved bracket of - `0.0350 … 0.1136` and a 95% sampling interval of `0.0848 … 0.1790`, from a - clean census with **zero** infrastructure failures and **zero** members whose - funding we claim not to exist — 54 of 83 reached a provenance class. Knowing a - member's funding class costs that pool roughly an order of magnitude of its - nominal anonymity. + and its limits published beside the number. + + **The pool measured is not this one.** It is Privacy Cash + (`9fhQBbumKEFuXtMBDw8AaQyAjCorLGJQiS3skWZdQyQD`), an unrelated and live Solana + mixer, because `mirror-pool` has no depositors and a measurement of our own + empty pool would be a measurement of nothing. So this is a tool pointed at + somebody else's protocol, and every number below describes theirs. + + **`ρ = 0.0955`**, inside an unresolved bracket of `0.0350 … 0.1136` and a 95% + sampling interval of `0.0848 … 0.1790`, from a clean census with **zero** + infrastructure failures and **zero** members whose funding we claim not to + exist — 54 of 83 reached a provenance class. Knowing a member's funding class + costs that pool roughly an order of magnitude of its nominal anonymity. The sample's class distribution is heavy-tailed and most of it was never observed — Good–Turing coverage 0.65, with Chao1 estimating 108 classes From 3838862a93990268176d50fada6bd686e7b9b4f9 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 18:56:55 -0300 Subject: [PATCH 60/83] docs: take the internal development record out of the deliverable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PLAN.md shipped a four-day schedule, an hour count, a list of what would be dropped first if time ran out, and a stretch section describing a contribution to a different repository as a second prize with a separate champion. None of that is about the protocol, and the last of it is strategy rather than work. What survives is the part a reader can use: the thesis, the design decisions and their reasons, and the record of where the shipped protocol departs from what was designed. That record is the reason to keep the file at all, so it is now the file's second half rather than a footnote to a plan. PROVENANCE_METHOD's prior-art section instructed itself — "claim exactly three things, all defensible", "useful framing", "do not cite" — which shows a reader the seams of the positioning instead of the finding. It now states the three claims, each with the check that would falsify it, and records the withdrawn paper as a fact rather than as an instruction. The methodological prescriptions elsewhere in that file stay: telling a reader not to use a set-valued class key is the method, not a note to self. --- docs/PLAN.md | 126 ++++++++++---------------------------- docs/PROVENANCE_METHOD.md | 51 ++++++++------- 2 files changed, 62 insertions(+), 115 deletions(-) diff --git a/docs/PLAN.md b/docs/PLAN.md index 1bf7f038..d215f3e4 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -160,103 +160,45 @@ chain time presented as a population; and a class key at raw-address resolution, which splits two members funded by the same exchange into different classes and so reports a larger anonymity set than exists. -## Schedule - -92 hours remain at the time of writing. Deadline is 2026-07-28 23:59 BRT. - -### Day 1 — primitives and circuit -`mirror-core`: Poseidon over BN254, the incremental Merkle accumulator with its -zero ladder, note commitments and nullifiers, and the byte layouts shared with -the program. `mirror-circuit`: the R1CS statement, key generation, the host -prover, and export of the verifying key in the on-chain layout. - -*Done when* a proof generated on the host verifies against the exported -verifying-key bytes, and an in-circuit Merkle root equals one computed natively. - -### Day 2 — the on-chain program -Instructions: `init_pool`, `deposit`, `open_epoch`, `submit_spend` (verify, burn -nullifier, write ticket), `settle_epoch`, `claim_reward`, `self_spend`. State: -pool, epoch, frontier accumulator with root history, nullifier PDAs, tickets. - -*Done when* the suite runs against the built `.so` and covers the accounting -invariant, replay rejection, `k`-floor rejection, action-binding mismatch, -malformed input on every handler, and a drain attempt of each shape described -above. CI green on fmt, clippy, tests and `build-sbf`. - -### Day 3 — measurement, CLI, setup ceremony -`mirror-provenance` against real mainnet data with the sample committed. The CLI -end to end: keygen, deposit, prove, spend, claim, and an `audit` command that -recomputes effective-`k` from committed data. Setup with a transcript that binds -the **full** verifying key and a circuit digest, and a `verify-setup` any third -party can run — checking every element, because a verifier that compares only -`delta` would green-light a verifying key belonging to a different circuit. - -### Day 4 — deploy, evidence, submission -Devnet soak producing signatures for every flow plus on-chain negative cases with -their error codes, then mainnet deployment. Documentation: README, ARCHITECTURE, -THREAT_MODEL with honest limits, PROOF, EFFECTIVE_K. Open the PR with time for -CI to finish, and submit on Earn. - -### Cut lines, in the order things get dropped - -Multi-party ceremony support degrades to a reproducible single-contributor setup -with the multi-party path documented. Dwell rewards degrade to a flat entry fee. -The provenance sample shrinks before its method weakens. **The accounting -invariant, the negative tests, the honest limitations section and a green CI are -never cut** — they are the difference between a privacy tool and a demo of one. - -### What actually got cut, and why - -This plan is as written on day zero. Six things in it did not ship, and each was -a decision rather than an overrun: - -- **`open_epoch` and epoch state.** Folded into `submit_spend` and - `settle_epoch`, which need none: a spend records its own timestamp and - settlement reads the clock. Four instructions instead of seven. +## What was cut, and why + +This document is the design as it was decided, so the places where the shipped +protocol departs from it are the interesting part. Six things here did not ship, +and each was a decision rather than an overrun. + +- **`open_epoch` and explicit epoch state.** Folded into `submit_spend` and + `settle_epoch`, which need neither: a spend records its own timestamp and + settlement reads the clock. Four instructions instead of seven, and one fewer + account whose lifecycle could disagree with the pool's. - **`claim_reward`, dwell rewards, and the entry fee with them.** The rewards - were cut to a flat entry fee, as the cut line above anticipated — and then the - fee was cut too, because a fee with no payout is not a simplification, it is a - fund trap. Fees accrue on the pool account; no instruction pays them out; and - none can be added without an authority this program deliberately does not - have. `Pool::initialise` now refuses any nonzero entry fee, so the trap is + went first. The fee that funded them went second, and for a sharper reason: a + fee with no payout is not a simplification, it is a fund trap. Fees would + accrue on the pool account, no instruction would pay them out, and none could + be added without an authority this program deliberately does not have. + `Pool::initialise` now refuses any nonzero entry fee, so the trap is unrepresentable rather than documented. The field stays in the layout for a version that ships a real payout path. - **`self_spend`.** Not built because it is not needed: a member relays for - themselves at zero fee and settles their own batch after the timeout. Same - exit, one fewer instruction, less attack surface. Pinned by + themselves at zero fee and settles their own batch once the timeout passes. + The same exit, one fewer instruction, less surface. Pinned by `a_member_can_always_exit_without_any_relay`. -- **The `audit` command.** Shipped as `analyze`. -- **"The measurement is load-bearing."** Withdrawn rather than descoped. A - program cannot check funding provenance, so the on-chain `k_floor` bounds - program-visible membership only and the measurement lives beside it. Claiming - otherwise would have been exactly the marketing line this plan disavows. -- **Mainnet.** Deliberately not deployed: the setup is reproducible rather than - secure, and a live pool with public toxic waste would invite deposits it cannot - protect. The README states the reasoning. +- **The `audit` command.** Shipped as `analyze`, alongside `compare` and + `selection`, which the design did not anticipate needing. +- **"The measurement is load-bearing."** Withdrawn rather than descoped. The + design above claimed a pool would refuse to settle below a measured effective + anonymity. A program cannot check funding provenance — the data is off-chain + and the classification is a judgement — so the on-chain `k_floor` bounds + program-visible membership only, and the measurement lives beside the protocol + instead of inside it. Descoping the claim while keeping the language would have + been the marketing line this document opens by disavowing. +- **Mainnet.** Deliberately not deployed. The trusted setup is reproducible + rather than secure, so a live pool would be inviting deposits it cannot + protect. `README.md` states the reasoning where a reader will meet it. `EFFECTIVE_K` shipped as `docs/PROVENANCE_METHOD.md`. -### Stretch, only if the above is complete - -A composability contribution to `account-cooker`, funding an agent fleet through -mirror-pool so that every agent wallet shares one provenance class. An agent -fleet is defeated by its common-funder graph long before its behaviour looks -wrong, and a shielded funding path is the piece that attacks it. It is a second -prize with a separate champion, and the work is small once the pool exists. - -## Risks - -**The circuit costs more than a day.** Mitigated: the two integration unknowns -that usually eat that day — the arkworks-to-Solana byte layout and the -Poseidon-to-gadget agreement — were resolved before implementation started. - -**RPC capacity for the provenance sample.** Mitigated by committing the raw -sample so the result reproduces offline, and by sizing the sample to the access -we actually have rather than to the number we would like to report. - -**Mainnet deployment cost.** A program of this size is a few SOL to deploy. -Devnet evidence is the fallback and is on the critical path regardless. - -**The field moves while we build.** The response is not to race anyone's line -count. It is to ship the thing this space declares open and unsolved, and to -make every number in it checkable by someone who does not trust us. +Two things shipped that the design did not contain at all: a selector that lets +the pool sign a call as a member's **authority**, which is what a stake +delegation needs and a transfer does not, and the measurement programme that +produced the eight runs in `docs/MEASUREMENT_LOG.md`. Both are described where +they live rather than here. diff --git a/docs/PROVENANCE_METHOD.md b/docs/PROVENANCE_METHOD.md index 040e358d..f141972e 100644 --- a/docs/PROVENANCE_METHOD.md +++ b/docs/PROVENANCE_METHOD.md @@ -1398,7 +1398,7 @@ Target crate: `crates/mirror-provenance`. **The differentiator is not a smaller number than anyone else's. It is that a reviewer can tell which direction our error goes.** -### 10.3 Prior art — what we may and may not claim +### 10.3 Prior art, and where this sits in it The funding-provenance channel is **not novel**. Wang et al., *On How Zero-Knowledge Proof Blockchain Mixers Improve, and Worsen User Privacy*, WWW 2023, Heuristic H4 "Intermediary @@ -1411,34 +1411,39 @@ Measured effect on TC 0.1 ETH (|OAS| = 11,941): H4 alone −4.20 %; all five heu combined −30.68 %. Their metric is Bayes vulnerability `Adv = 1/|SAS|` — identical to our min-entropy rung. -**Claim exactly three things, all defensible:** - -1. **First on Solana.** DBLP's full index returns six Solana blockchain papers (phishing, - rug detection, transaction failure, Jito MEV, NFT ecosystem, SolRPDS) — **none** on - privacy, anonymity, deanonymization, address clustering, mixers or shielded pools. - arXiv full-text agrees. Elusiv sunset 2024-02-29 (team → Arcium, general MPC) and was - never measured; Light Protocol pivoted to ZK Compression; Privacy Cash (launched - 2025-08-27) is the most-used Solana ZK mixer and has never been measured. -2. **Entity-level label ladder with reported sensitivity**, where all prior work — Wang et - al. included — fixes one resolution. -3. **`ρ = 2^{−H(C)}` as a k-independent headline**, where all prior work reports a - percentage reduction for one pool at one size. - -Useful framing: SPL Token-2022 confidential transfers hide *amounts and balances only* — -sender and receiver addresses stay public — so by construction the advertised anonymity -set is exactly 1. Solana's flagship privacy primitive has no anonymity set, and the one -protocol class where an advertised-vs-effective gap could exist has never been studied. +Three things here are new, and each is stated so that a reader can check it rather than +take it: + +1. **This is the first such measurement on Solana.** DBLP's full index returns six Solana + blockchain papers — phishing, rug detection, transaction failure, Jito MEV, the NFT + ecosystem, SolRPDS — and **none** on privacy, anonymity, deanonymization, address + clustering, mixers or shielded pools. arXiv full-text agrees. Of the protocols + themselves: Elusiv sunset on 2024-02-29 (its team moved to Arcium and general MPC) and + was never measured; Light Protocol pivoted to ZK Compression; Privacy Cash, launched + 2025-08-27 and the most-used Solana ZK mixer, has never been measured. The search is + reproducible and a counter-example would settle it. +2. **The label ladder is entity-level, with its sensitivity reported.** Prior work, + Wang et al. included, fixes a single resolution and reports the number it produces. +3. **`ρ = 2^{−H(C)}` is a k-independent headline.** Prior work reports a percentage + reduction for one pool at one size, which cannot be compared across pools or + extrapolated to a larger one. + +For scale: SPL Token-2022 confidential transfers hide *amounts and balances only* — sender +and receiver addresses stay public — so the advertised anonymity set is exactly 1 by +construction. Solana's flagship privacy primitive has no anonymity set, and the one +protocol class where an advertised-versus-effective gap could exist had not been studied. **Comparanda for our result:** Tutela (arXiv:2201.06811) −37 % ± 15 %; Wang et al. −27.34 % (ETH) / −46.02 % (BSC); Béres et al. (IEEE DAPPS 2021) anonymity set 400 → ~12 under a one-day timing assumption; Kappos et al. (USENIX Security 2018, Zcash) −69.1 % **of value, not of members**; Möser et al. (PoPETs 2018, Monero) ring size 11 → effective 1.16–1.80. -**Do not cite arXiv:2510.09433** (Cristodaro, Kraner & Tessone, Tornado Cash cross-chain -clustering). It was **withdrawn** at v3 on 2025-11-18: *"This paper has been withdrawn by -the author due to mistakes in the references"* **[V]**. Its numbers still circulate -widely in search results and are easy to pick up second-hand, which is exactly why the -withdrawal is recorded here rather than the paper simply being left uncited. +One paper is deliberately absent from those comparanda. arXiv:2510.09433 (Cristodaro, +Kraner & Tessone, Tornado Cash cross-chain clustering) was **withdrawn** at v3 on +2025-11-18 — *"This paper has been withdrawn by the author due to mistakes in the +references"*. Its numbers still surface in search results and are easy to pick up +second-hand, so the withdrawal is recorded here rather than the paper being silently +left uncited. **Terminology.** "Provenance class" is not standard. Define it once against the established vocabulary: *"we partition the anonymity set into* **provenance classes**, From 95a464ebf482cb1f17705a5b4b3cd4b3e1475f58 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 19:22:11 -0300 Subject: [PATCH 61/83] feat: a member can actually use the pool, from a machine that has never seen it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The tool had nine commands and none of them let a member do anything. Two were trusted setup, six were the measurement, one was a scripted demo. Somebody who wanted to deposit had to read soak.rs and reimplement it. For a tool, that is the gap that matters most. Six commands now cover the whole journey: init-pool, note-new, deposit, tree, spend, settle. Every one of them was run against devnet to produce the output in docs/USAGE.md; nothing there is illustrative. `tree` is the piece the rest stands on. The program keeps only the accumulator's frontier — one node per level, enough to append a leaf and produce a root, not enough to prove a particular leaf is in the tree — so a client needs the whole leaf set. The soak had been carrying a local ledger file to dodge this, which is convenience masquerading as necessity: every commitment was an argument to a Deposit instruction, so the set is in the transaction history in insertion order. `tree` reads it back, rebuilds the accumulator, and checks the root against the pool's. A match proves the recovered set is complete and correctly ordered, which is exactly the precondition a membership proof needs. That is the difference between a demo and a tool. A member with their note file can act from a fresh machine. No indexer, no server, no account with anybody, and no operator — us included — between a member and their own money. The scan reads the *pool* account rather than the program, because a program hosts one pool per denomination and scanning the program would walk every pool's history to build one pool's tree. On the experience, three decisions worth naming: - The note file refuses to be overwritten and there is no --force. An overwrite destroys a deposit. - A note whose commitment disagrees with its secrets is refused before it can be spent. Spending it would burn a nullifier against a leaf that is not in the tree — the member loses the deposit and learns nothing about why. - Below the crowd floor, `settle` says how many spends are pending, what the floor is, how long the youngest has waited and how long remains. The alternative was a failed transaction carrying custom error 0x15. `settle` executes plain transfers and leaves CPI actions alone, reporting each one. The record binds how many accounts a call takes and never which, so no tool can infer the account list a callee expects; pretending otherwise would produce confident wrong guesses. Ten tests on the new code, all offline: the note round-trip, three ways a note file can lie, the overwrite refusal, and — the one that matters — that a tree rebuilt in the wrong order produces a different root. That property is why the scan reverses the RPC's newest-first paging, and without the test it would be a comment. --- README.md | 40 ++- crates/mirror-cli/src/chain.rs | 86 +++++ crates/mirror-cli/src/client.rs | 541 +++++++++++++++++++++++++++++++ crates/mirror-cli/src/history.rs | 200 ++++++++++++ crates/mirror-cli/src/main.rs | 213 ++++++++++++ crates/mirror-cli/src/note.rs | 196 +++++++++++ crates/mirror-cli/src/soak.rs | 2 +- docs/USAGE.md | 222 +++++++++++++ 8 files changed, 1497 insertions(+), 3 deletions(-) create mode 100644 crates/mirror-cli/src/client.rs create mode 100644 crates/mirror-cli/src/history.rs create mode 100644 crates/mirror-cli/src/note.rs create mode 100644 docs/USAGE.md diff --git a/README.md b/README.md index 4dbe33aa..d62a12c7 100644 --- a/README.md +++ b/README.md @@ -79,13 +79,48 @@ not say. There is a section below of things we deliberately do not claim. | `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain. | | `crates/mirror-circuit` | R1CS gadget, membership circuit, prover, key export. | | `crates/mirror-provenance` | The funding-provenance measurement. | -| `crates/mirror-cli` | `setup`, `verify-setup`, `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection`, `soak`. | +| `crates/mirror-cli` | The tool. `init-pool`, `note-new`, `deposit`, `tree`, `spend`, `settle` for members; `setup`, `verify-setup`, `soak` for operators; `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection` for the measurement. | **198 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. +## Using it + +``` +mirror note-new --denomination D --out m1.json # a note is a local secret +mirror deposit --note m1.json # escrow it, join the set +mirror tree # rebuild the accumulator from chain +mirror spend --note m1.json --to --relay relay.json +mirror settle # permissionless +``` + +`docs/USAGE.md` is the walkthrough, and every line of output in it was produced +by running the command against devnet. + +**No server, no indexer, no account with anybody.** The program stores only the +accumulator's frontier — enough to append a leaf, not enough to prove one is +there — so a client needs the whole leaf set. `mirror tree` recovers it from the +transaction history, rebuilds the accumulator, and checks the root against the +one the program holds: + +``` + leaves recovered 2 + pool reports 2 + rebuilt root 0c77cb909067c1a57811be8c05237aff2715c65c6250fdde764ed768096cd732 + on-chain root 0c77cb909067c1a57811be8c05237aff2715c65c6250fdde764ed768096cd732 +``` + +A matching root proves the recovered set is complete and correctly ordered, which +is the precondition a membership proof needs. It means a member can act from a +machine that has never seen the pool, carrying nothing but their note file — and +that no operator, including us, sits between a member and their own money. + +The relay signs and the member never does, so no member key appears on chain +after the deposit. Below the crowd floor, `settle` says what it is waiting for +and why rather than returning an error code. + ## Synchronised actions are the point The brief asks for an anonymity set for *behaviour*, not for funds. That @@ -477,4 +512,5 @@ that keeps only its successful runs is selecting rather than reporting. | `docs/MEASUREMENT_LOG.md` | Every run. | | `docs/THREAT_MODEL.md` | The adversary, what holds, and every place it stops. | | `docs/PROOF.md` | Devnet signatures for every flow, and the rejections. | -| `docs/PLAN.md` | What was planned, and what was cut. | +| `docs/USAGE.md` | Every command, with real output. | +| `docs/PLAN.md` | The design as decided, and where the shipped protocol departs from it. | diff --git a/crates/mirror-cli/src/chain.rs b/crates/mirror-cli/src/chain.rs index a2a25253..886f327d 100644 --- a/crates/mirror-cli/src/chain.rs +++ b/crates/mirror-cli/src/chain.rs @@ -182,6 +182,92 @@ impl Chain { .collect()) } + /// Every signature that touched `address`, oldest first. + /// + /// Pages until the cluster runs out. The RPC returns newest first and pages + /// backwards through `before`, so the reversal at the end is what turns this + /// into an insertion order — and insertion order is the whole point, because + /// a Merkle accumulator rebuilt in the wrong order produces a different root + /// and no proof against it will ever verify. + /// + /// Failed transactions are dropped here rather than by the caller. They + /// changed no state, so a rebuild that included them would insert leaves the + /// chain never inserted. + pub fn signatures_for_address(&self, address: &Pubkey) -> Result> { + let mut out: Vec = Vec::new(); + let mut before: Option = None; + loop { + let params = match &before { + Some(b) => serde_json::json!([ + address.to_string(), + { "limit": 1000, "before": b, "commitment": "confirmed" } + ]), + None => serde_json::json!([ + address.to_string(), + { "limit": 1000, "commitment": "confirmed" } + ]), + }; + let page = self.call("getSignaturesForAddress", params)?; + let entries = page + .as_array() + .ok_or_else(|| anyhow!("getSignaturesForAddress: not an array"))?; + if entries.is_empty() { + break; + } + for entry in entries { + let sig = entry + .get("signature") + .and_then(|s| s.as_str()) + .ok_or_else(|| anyhow!("getSignaturesForAddress: entry without signature"))?; + before = Some(sig.to_string()); + if entry.get("err").map(|e| !e.is_null()).unwrap_or(false) { + continue; + } + out.push(sig.to_string()); + } + if entries.len() < 1000 { + break; + } + } + out.reverse(); + Ok(out) + } + + /// A landed transaction, decoded. + /// + /// Asked for as base64 and deserialised here rather than read out of the + /// RPC's parsed JSON, because parsed instruction data arrives base58-encoded + /// and decoding that would mean carrying an alphabet this crate otherwise + /// has no use for. + pub fn transaction(&self, signature: &str) -> Result> { + let v = self.call( + "getTransaction", + serde_json::json!([ + signature, + { + "commitment": "confirmed", + "encoding": "base64", + "maxSupportedTransactionVersion": 0 + } + ]), + )?; + if v.is_null() { + return Ok(None); + } + let encoded = v + .pointer("/transaction/0") + .and_then(|t| t.as_str()) + .ok_or_else(|| anyhow!("getTransaction: {signature} carried no transaction"))?; + let raw = base64::engine::general_purpose::STANDARD + .decode(encoded) + .map_err(|e| anyhow!("getTransaction: {signature} is not base64: {e}"))?; + // A versioned transaction will not deserialise into this shape. Treating + // that as "not one of ours" is correct: this program is only ever called + // from legacy transactions built by this CLI, and a versioned one that + // happened to touch the pool carries nothing we need. + Ok(bincode::deserialize::(&raw).ok()) + } + pub fn balance(&self, key: &Pubkey) -> Result { let v = self.call( "getBalance", diff --git a/crates/mirror-cli/src/client.rs b/crates/mirror-cli/src/client.rs new file mode 100644 index 00000000..e130f52c --- /dev/null +++ b/crates/mirror-cli/src/client.rs @@ -0,0 +1,541 @@ +//! The member-facing half of the tool: join a pool, act through it, settle it. +//! +//! Every command here reads what it needs from the chain. Nothing depends on a +//! file this tool wrote earlier except the note itself, and nothing depends on a +//! server we run. A member who keeps their note file can act from a fresh +//! machine; a member who loses it has lost the deposit, and no operator can +//! change that in either direction. + +use crate::chain::Chain; +use crate::history::{self, History}; +use crate::note::StoredNote; +use anyhow::{anyhow, Context, Result}; +use ark_std::rand::SeedableRng; +use mirror_pool_program::{ + instruction::Instruction as MirrorIx, + pda::{pool_address, spend_address, vault_address}, + processor::{SELECTOR_INVOKE, SELECTOR_INVOKE_SIGNED, SELECTOR_TRANSFER}, + spend::{Spend, STATUS_PENDING}, + Pool, +}; +use solana_keypair::Keypair; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use solana_signer::Signer; +use solana_transaction::Transaction; +use std::path::Path; + +/// What the pool account says about itself. +pub struct PoolState { + pub pool: Pubkey, + pub vault: Pubkey, + pub denomination: u64, + pub k_floor: u32, + pub deposits: u64, + pub spends: u64, + pub root: [u8; 32], +} + +pub fn read_pool(chain: &Chain, program_id: &Pubkey, denomination: u64) -> Result { + let (pool, _) = pool_address(program_id, denomination); + let (vault, _) = vault_address(program_id, &pool); + let mut data = chain.account_data(&pool)?.ok_or_else(|| { + anyhow!( + "no pool exists at {pool} for denomination {denomination}. \ + Pool creation is permissionless — `mirror init-pool` makes one." + ) + })?; + let state = + Pool::load(&mut data).map_err(|e| anyhow!("{pool} is not a pool account: {e:?}"))?; + Ok(PoolState { + pool, + vault, + denomination: state.denomination(), + k_floor: state.k_floor(), + deposits: state.deposit_count(), + spends: state.spend_count(), + root: state + .current_root() + .map_err(|e| anyhow!("{e:?}"))? + .to_bytes(), + }) +} + +fn send(chain: &Chain, ix: Instruction, signers: &[&Keypair]) -> Result { + let blockhash = chain.latest_blockhash()?; + let message = solana_message::Message::new(&[ix], Some(&signers[0].pubkey())); + let tx = Transaction::new(signers, message, blockhash); + chain.send(&tx) +} + +// --------------------------------------------------------------------------- +// init-pool + +pub fn init_pool( + chain: &Chain, + program_id: &Pubkey, + denomination: u64, + k_floor: u32, + payer: &Keypair, +) -> Result<()> { + let (pool, _) = pool_address(program_id, denomination); + let (vault, _) = vault_address(program_id, &pool); + if chain.account_data(&pool)?.is_some() { + println!("the pool for denomination {denomination} already exists at {pool}"); + println!("nothing to do — one pool per denomination is the whole point"); + return Ok(()); + } + let ix = Instruction::new_with_bytes( + *program_id, + &MirrorIx::InitPool { + denomination, + entry_fee: 0, + k_floor, + } + .pack(), + vec![ + AccountMeta::new(payer.pubkey(), true), + AccountMeta::new(pool, false), + AccountMeta::new(vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let sig = send(chain, ix, &[payer])?; + println!("pool {pool}"); + println!("vault {vault}"); + println!("signature {sig}"); + Ok(()) +} + +// --------------------------------------------------------------------------- +// deposit + +pub fn deposit( + chain: &Chain, + program_id: &Pubkey, + note_path: &Path, + depositor: &Keypair, +) -> Result<()> { + let stored = StoredNote::read(note_path)?; + let note = stored.note()?; + let state = read_pool(chain, program_id, stored.denomination)?; + let commitment = stored.commitment_bytes()?; + + // Refuse a second deposit of the same note before it costs anything. The + // program allows duplicate commitments — the only party a duplicate harms is + // whoever paid for it — but a member doing it by accident is out a + // denomination for a leaf they cannot distinguish from their first. + let history = history::scan(chain, program_id, &state.pool, false)?; + if history.index_of(&commitment).is_some() { + return Err(anyhow!( + "this note's commitment is already a leaf of this pool. Depositing it \ + again would escrow a second denomination against a note you cannot \ + tell apart from the first." + )); + } + + let balance = chain.balance(&depositor.pubkey())?; + if balance < state.denomination { + return Err(anyhow!( + "{} holds {} lamports and this pool escrows {} per deposit", + depositor.pubkey(), + balance, + state.denomination + )); + } + + let ix = Instruction::new_with_bytes( + *program_id, + &MirrorIx::Deposit { commitment }.pack(), + vec![ + AccountMeta::new(depositor.pubkey(), true), + AccountMeta::new(state.pool, false), + AccountMeta::new(state.vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let sig = send(chain, ix, &[depositor])?; + println!( + "deposited {} lamports into {}", + state.denomination, state.pool + ); + println!("leaf {}", state.deposits); + println!("signature {sig}"); + println!(); + println!( + "Keep {} safe. It is the only way to spend this note,", + note_path.display() + ); + println!("and nobody — including this pool's authors — can reissue it."); + let _ = note; + Ok(()) +} + +// --------------------------------------------------------------------------- +// tree + +/// Rebuilds the accumulator from chain history and checks it against the pool. +/// +/// The check is the point. A rebuilt tree whose root matches the one the program +/// holds is proof that the history is complete and correctly ordered, which is +/// exactly the precondition a membership proof needs. A mismatch means any proof +/// built here would fail on-chain for reasons that would be very hard to read +/// backwards from the failure. +pub fn tree(chain: &Chain, program_id: &Pubkey, denomination: u64) -> Result { + let state = read_pool(chain, program_id, denomination)?; + println!("pool {}", state.pool); + println!("vault {}", state.vault); + println!("denomination {}", state.denomination); + println!("k floor {}", state.k_floor); + println!( + "notes {} deposited, {} settled, {} outstanding", + state.deposits, + state.spends, + state.deposits.saturating_sub(state.spends) + ); + println!(); + println!("rebuilding the accumulator from chain history:"); + + let history = history::scan(chain, program_id, &state.pool, true)?; + let rebuilt = history.tree()?; + let root = rebuilt.root().map_err(|e| anyhow!("{e:?}"))?; + + println!(); + println!(" leaves recovered {}", history.commitments.len()); + println!(" pool reports {}", state.deposits); + println!(" rebuilt root {}", hex::encode(root.to_bytes())); + println!(" on-chain root {}", hex::encode(state.root)); + + if history.commitments.len() as u64 != state.deposits { + return Err(anyhow!( + "recovered {} leaves but the pool has inserted {}. The history is \ + incomplete — most likely the endpoint has pruned it. `mirror \ + check-endpoint` tests for exactly that.", + history.commitments.len(), + state.deposits + )); + } + if root.to_bytes() != state.root { + return Err(anyhow!( + "the rebuilt root does not match the pool's. The leaf set or its \ + order is wrong, and any proof built against this tree would be \ + rejected on-chain." + )); + } + println!(); + println!(" the rebuilt tree matches the chain — proofs built from it will verify"); + Ok(history) +} + +// --------------------------------------------------------------------------- +// spend + +/// What a member is asking the pool to do. +pub enum Action { + /// Pay the beneficiary, no CPI. + Transfer, + /// Call `target` with `payload`. `pool_signs` picks the selector that hands + /// the vault to the callee as a signer. + Invoke { + target: Pubkey, + payload: Vec, + accounts: u8, + pool_signs: bool, + }, +} + +impl Action { + fn selector(&self) -> u64 { + match self { + Action::Transfer => SELECTOR_TRANSFER, + Action::Invoke { + pool_signs: false, .. + } => SELECTOR_INVOKE, + Action::Invoke { + pool_signs: true, .. + } => SELECTOR_INVOKE_SIGNED, + } + } + fn target(&self) -> [u8; 32] { + match self { + Action::Transfer => [0u8; 32], + Action::Invoke { target, .. } => target.to_bytes(), + } + } + fn payload(&self) -> &[u8] { + match self { + Action::Transfer => &[], + Action::Invoke { payload, .. } => payload, + } + } + fn accounts(&self) -> u8 { + match self { + Action::Transfer => 0, + Action::Invoke { accounts, .. } => *accounts, + } + } +} + +#[allow(clippy::too_many_arguments)] +pub fn spend( + chain: &Chain, + program_id: &Pubkey, + note_path: &Path, + beneficiary: &Pubkey, + relay: &Keypair, + relay_fee: u64, + action: Action, +) -> Result<()> { + let stored = StoredNote::read(note_path)?; + let note = stored.note()?; + let state = read_pool(chain, program_id, stored.denomination)?; + + if relay.pubkey() == *beneficiary { + eprintln!( + "warning: the relay and the beneficiary are the same key, which links \ + the payout to whoever paid the fee." + ); + } + if relay_fee >= state.denomination { + return Err(anyhow!( + "a relay fee of {relay_fee} is not less than the denomination {}", + state.denomination + )); + } + if state.deposits < state.k_floor as u64 { + return Err(anyhow!( + "this pool holds {} notes and its floor is {}. It refuses to act \ + below the floor, because a crowd of one is not a crowd.", + state.deposits, + state.k_floor + )); + } + + println!("rebuilding the accumulator so this note can be proved a member:"); + let history = history::scan(chain, program_id, &state.pool, true)?; + let commitment = stored.commitment_bytes()?; + let index = history.index_of(&commitment).ok_or_else(|| { + anyhow!( + "this note is not a leaf of the pool. Either it was never deposited, \ + or it belongs to a different denomination." + ) + })?; + let tree = history.tree()?; + let root = tree.root().map_err(|e| anyhow!("{e:?}"))?; + if root.to_bytes() != state.root { + return Err(anyhow!( + "the rebuilt root does not match the pool's, so a proof against it \ + would be rejected. Run `mirror tree` to see the mismatch." + )); + } + println!( + " this note is leaf {index} of {}", + history.commitments.len() + ); + + let binding = mirror_core::action_binding( + action.selector(), + &action.target(), + &beneficiary.to_bytes(), + relay_fee, + action.accounts(), + action.payload(), + ); + let merkle_proof = tree.proof(index).map_err(|e| anyhow!("{e:?}"))?; + let witness = mirror_circuit::Witness { + note, + merkle_proof: &merkle_proof, + root, + action_binding: binding, + }; + + println!("deriving the proving key from the published seed (this takes a moment)"); + let keys = crate::soak::keys()?; + println!("proving membership"); + let mut rng = ark_std::rand::rngs::StdRng::from_entropy(); + let proof = mirror_circuit::prove(&keys, &witness, &mut rng).map_err(|e| anyhow!("{e:?}"))?; + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(program_id, &state.pool, &nullifier); + + let ix = Instruction::new_with_bytes( + *program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: action.selector(), + target_program: action.target(), + beneficiary: beneficiary.to_bytes(), + relay_fee, + action_accounts: action.accounts(), + payload: action.payload().to_vec(), + } + .pack(), + vec![ + AccountMeta::new(relay.pubkey(), true), + AccountMeta::new(state.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let sig = send(chain, ix, &[relay])?; + + println!(); + println!("submitted. The note is spent and the action is recorded."); + println!(" nullifier {}", hex::encode(nullifier)); + println!(" record {spend_pda}"); + println!(" signature {sig}"); + println!(); + println!("Nothing has been paid out yet — settlement executes the batch, which is"); + println!("what gives every member's action one timestamp. Run `mirror settle`, or"); + println!("wait for anyone else to."); + Ok(()) +} + +// --------------------------------------------------------------------------- +// settle + +pub fn settle( + chain: &Chain, + program_id: &Pubkey, + denomination: u64, + settler: &Keypair, + now: i64, +) -> Result<()> { + let state = read_pool(chain, program_id, denomination)?; + println!("looking for spends waiting to settle:"); + let history = history::scan(chain, program_id, &state.pool, true)?; + + let mut ready: Vec<(Pubkey, Pubkey, Pubkey, i64)> = Vec::new(); + let mut skipped_actions: Vec<([u8; 32], u64, u8)> = Vec::new(); + for submitted in &history.spends { + let Some(mut data) = chain.account_data(&submitted.spend)? else { + continue; + }; + let Ok(record) = Spend::load(&mut data) else { + continue; + }; + if record.status() != STATUS_PENDING { + continue; + } + if record.selector() != SELECTOR_TRANSFER { + // A CPI action needs the account list its callee expects, which this + // command cannot infer from the chain — the record binds how many + // accounts, never which. Settling those is a caller's job. + skipped_actions.push(( + submitted.nullifier, + record.selector(), + record.action_accounts(), + )); + continue; + } + ready.push(( + submitted.spend, + Pubkey::new_from_array(record.beneficiary()), + Pubkey::new_from_array(record.relay()), + record.submitted_at(), + )); + } + + if !skipped_actions.is_empty() { + println!( + " {} pending action(s) left alone: a CPI needs the account list its callee", + skipped_actions.len() + ); + println!(" expects, and the record binds how many accounts, never which."); + for (nullifier, selector, accounts) in &skipped_actions { + println!( + " nullifier {} — selector {selector}, {accounts} account(s)", + hex::encode(nullifier) + ); + } + } + if ready.is_empty() { + println!(" nothing to settle"); + return Ok(()); + } + + // The crowd rule, checked here so a caller learns why rather than reading a + // custom error code off a failed transaction. + let crowd = ready.len() as u32 >= state.k_floor; + let timeout = mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS; + if !crowd { + let youngest = ready.iter().map(|(_, _, _, at)| *at).max().unwrap_or(0); + let waited = now.saturating_sub(youngest); + if waited < timeout { + println!(); + println!( + " {} spend(s) pending, and this pool's floor is {}.", + ready.len(), + state.k_floor + ); + println!( + " A batch below the floor may settle once every spend in it has waited {timeout}s;" + ); + println!( + " the youngest has waited {waited}s, so {}s remain.", + timeout - waited + ); + println!(); + println!(" This is a liveness guarantee rather than a restriction: nobody can"); + println!(" hold a member's funds waiting for a crowd that never arrives."); + return Ok(()); + } + } + + let mut metas = vec![ + AccountMeta::new(settler.pubkey(), true), + AccountMeta::new(state.pool, false), + AccountMeta::new(state.vault, false), + ]; + for (spend, beneficiary, relay, _) in &ready { + metas.push(AccountMeta::new(*spend, false)); + metas.push(AccountMeta::new(*beneficiary, false)); + metas.push(AccountMeta::new(*relay, false)); + } + let ix = Instruction::new_with_bytes( + *program_id, + &MirrorIx::SettleEpoch { + count: ready.len() as u8, + } + .pack(), + metas, + ); + let sig = send(chain, ix, &[settler])?; + println!(); + println!("settled {} spends in one transaction", ready.len()); + println!(" signature {sig}"); + println!(); + println!("Every payout in that batch shares one timestamp and one ordering, which"); + println!("is what stops arrival time from telling the members apart."); + Ok(()) +} + +/// Creates a note and writes it, printing what to do next. +pub fn note_new(denomination: u64, path: &Path) -> Result<()> { + let note = crate::note::generate(denomination)?; + let stored = StoredNote::from_note(¬e, denomination)?; + stored.write(path)?; + println!("wrote {}", path.display()); + println!(" denomination {denomination}"); + println!(" commitment {}", stored.commitment); + println!(); + println!("This file is the note. Treat it exactly as you would a keypair:"); + println!("anyone holding it can spend the deposit, and losing it loses the"); + println!("deposit with no way back."); + println!(); + println!("Next: mirror deposit --note {}", path.display()); + Ok(()) +} + +/// The current wall clock, for the crowd-rule explanation. +pub fn now() -> Result { + Ok(std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .context("reading the clock")? + .as_secs() as i64) +} diff --git a/crates/mirror-cli/src/history.rs b/crates/mirror-cli/src/history.rs new file mode 100644 index 00000000..f11f9d1b --- /dev/null +++ b/crates/mirror-cli/src/history.rs @@ -0,0 +1,200 @@ +//! Rebuilding a pool's state from the chain, with no index and no local file. +//! +//! The program stores the accumulator's *frontier* — one node per level — which +//! is all it needs to append a leaf and produce a root, and is not enough to +//! prove that any particular leaf is in the tree. A member needs the whole leaf +//! set for that. +//! +//! The leaves are not lost, though: every commitment was an argument to a +//! `Deposit` instruction, so the complete set is sitting in the transaction +//! history in the order it was inserted. This module reads it back. +//! +//! That matters beyond convenience. A client that depended on a local note +//! ledger would strand a member who lost the file, and one that depended on an +//! indexer would put a server between a member and their own money. Neither is +//! acceptable for a tool whose entire premise is that you should not have to +//! trust the operator — including us. + +use crate::chain::Chain; +use anyhow::{anyhow, Result}; +use mirror_core::{Field, MerkleTree}; +use mirror_pool_program::{instruction::Instruction as MirrorIx, pda::spend_address}; +use solana_program::pubkey::Pubkey; + +/// Where the pool account sits in the account list of the two instructions this +/// module reads. Both put the signer first and the pool second. +const POOL_ACCOUNT_INDEX: usize = 1; + +/// A spend that was submitted, and where its record lives. +pub struct SubmittedSpend { + pub nullifier: [u8; 32], + pub spend: Pubkey, +} + +/// Everything a client needs that the pool account does not hold. +pub struct History { + /// Note commitments in insertion order. Index `i` is leaf `i`. + pub commitments: Vec<[u8; 32]>, + pub spends: Vec, +} + +impl History { + /// Rebuilds the host-side Merkle tree the prover needs. + pub fn tree(&self) -> Result { + let mut tree = MerkleTree::new().map_err(|e| anyhow!("{e:?}"))?; + for (i, c) in self.commitments.iter().enumerate() { + let leaf = Field::from_bytes(*c) + .map_err(|e| anyhow!("commitment {i} is not a canonical field element: {e:?}"))?; + tree.insert(leaf).map_err(|e| anyhow!("{e:?}"))?; + } + Ok(tree) + } + + /// The index of `commitment`, if this pool holds it. + pub fn index_of(&self, commitment: &[u8; 32]) -> Option { + self.commitments + .iter() + .position(|c| c == commitment) + .map(|i| i as u64) + } +} + +/// Reads every deposit and spend this pool has seen, oldest first. +/// +/// Scans the *pool* account rather than the program. A program can host one pool +/// per denomination, so scanning the program would walk every pool's history to +/// build one pool's tree, and would then have to discard most of it. The pool +/// account appears in exactly the transactions that concern it. +pub fn scan(chain: &Chain, program_id: &Pubkey, pool: &Pubkey, verbose: bool) -> Result { + let signatures = chain.signatures_for_address(pool)?; + if verbose { + println!(" {} transactions touched this pool", signatures.len()); + } + + let mut commitments = Vec::new(); + let mut spends = Vec::new(); + + for (n, signature) in signatures.iter().enumerate() { + if verbose && n > 0 && n % 25 == 0 { + println!(" {n}/{} …", signatures.len()); + } + let Some(tx) = chain.transaction(signature)? else { + continue; + }; + let keys = &tx.message.account_keys; + + for ix in &tx.message.instructions { + let Some(program) = keys.get(ix.program_id_index as usize) else { + continue; + }; + if program != program_id { + continue; + } + // An instruction of ours that names a different pool belongs to a + // different denomination and a different tree. + let names_this_pool = ix + .accounts + .get(POOL_ACCOUNT_INDEX) + .and_then(|i| keys.get(*i as usize)) + .map(|k| k == pool) + .unwrap_or(false); + if !names_this_pool { + continue; + } + // Anything that does not decode is not ours to interpret. The pool + // account can appear in a transaction for reasons this module has no + // opinion about. + let Ok(decoded) = MirrorIx::unpack(&ix.data) else { + continue; + }; + match decoded { + MirrorIx::Deposit { commitment } => commitments.push(commitment), + MirrorIx::SubmitSpend { nullifier, .. } => { + let (spend, _) = spend_address(program_id, pool, &nullifier); + spends.push(SubmittedSpend { nullifier, spend }); + } + _ => {} + } + } + } + + Ok(History { + commitments, + spends, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use mirror_core::Frontier; + + fn commitments(n: u64) -> Vec<[u8; 32]> { + (1..=n) + .map(|i| Field::from_u64(i * 1_000 + 7).to_bytes()) + .collect() + } + + /// The rebuilt tree must agree with the accumulator the program keeps. + /// + /// This is the property the whole module exists for, and it is a property of + /// the *order*: the same leaves inserted in a different order give a + /// different root, so a scan that returned them newest-first would produce a + /// tree that looks healthy and against which no proof verifies. + #[test] + fn a_rebuilt_tree_matches_the_frontier_the_program_keeps() { + let history = History { + commitments: commitments(9), + spends: Vec::new(), + }; + let mut frontier = Frontier::new().unwrap(); + for c in &history.commitments { + frontier.insert(Field::from_bytes(*c).unwrap()).unwrap(); + } + assert_eq!(history.tree().unwrap().root().unwrap(), frontier.root()); + } + + #[test] + fn the_wrong_order_produces_a_different_root() { + let forward = History { + commitments: commitments(5), + spends: Vec::new(), + }; + let mut backward = forward.commitments.clone(); + backward.reverse(); + let reversed = History { + commitments: backward, + spends: Vec::new(), + }; + assert_ne!( + forward.tree().unwrap().root().unwrap(), + reversed.tree().unwrap().root().unwrap(), + "insertion order does not affect the root, which would mean the scan \ + order does not matter — it does" + ); + } + + #[test] + fn a_commitment_is_found_at_the_index_it_was_inserted() { + let history = History { + commitments: commitments(6), + spends: Vec::new(), + }; + for (i, c) in history.commitments.clone().iter().enumerate() { + assert_eq!(history.index_of(c), Some(i as u64)); + } + assert_eq!(history.index_of(&[0xAB; 32]), None); + } + + /// A leaf the accumulator would reject must be refused while it is still a + /// bad byte string, not deep inside a Poseidon call. + #[test] + fn a_non_canonical_commitment_fails_the_rebuild_by_name() { + let history = History { + commitments: vec![[0xff; 32]], + spends: Vec::new(), + }; + let err = history.tree().unwrap_err().to_string(); + assert!(err.contains("canonical"), "{err}"); + } +} diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 81a1e597..1dbb903b 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -6,8 +6,16 @@ use sha2::{Digest, Sha256}; use std::path::PathBuf; mod chain; +mod client; +mod history; +mod note; mod soak; +/// The public devnet cluster, so the common case needs no flag. +const DEFAULT_URL: &str = "https://api.devnet.solana.com"; +/// Where the Solana CLI keeps its default keypair. +const DEFAULT_KEYPAIR: &str = "~/.config/solana/id.json"; + /// The seed the committed verifying key was generated from. /// /// Public on purpose. It makes the setup reproducible — anyone can re-derive the @@ -198,6 +206,113 @@ enum Command { #[arg(long)] expect: Option, }, + + /// Creates a pool for a denomination. Permissionless, and one per size. + InitPool { + #[arg(long)] + program: String, + /// Lamports each deposit escrows. This also names the pool. + #[arg(long)] + denomination: u64, + /// Notes the pool must hold before it will act. + #[arg(long, default_value_t = 2)] + k_floor: u32, + #[arg(long, default_value = DEFAULT_URL)] + url: String, + #[arg(long, default_value = DEFAULT_KEYPAIR)] + keypair: String, + }, + + /// Draws a fresh note and writes its secret to a file. + /// + /// The file is the deposit. Nobody can reissue it, which is the same + /// property that means nobody can freeze it. + NoteNew { + /// The pool this note will join, named by its denomination. + #[arg(long)] + denomination: u64, + /// Where to write it. Refuses to overwrite. + #[arg(long, default_value = "note.json")] + out: PathBuf, + }, + + /// Escrows a denomination and adds the note to the anonymity set. + Deposit { + #[arg(long)] + program: String, + #[arg(long)] + note: PathBuf, + #[arg(long, default_value = DEFAULT_URL)] + url: String, + #[arg(long, default_value = DEFAULT_KEYPAIR)] + keypair: String, + }, + + /// Rebuilds the accumulator from chain history and checks it against the pool. + /// + /// Needs no indexer and no local file. The check is what makes a membership + /// proof trustworthy: a rebuilt root that matches the chain's is proof the + /// recovered leaf set is complete and correctly ordered. + Tree { + #[arg(long)] + program: String, + #[arg(long)] + denomination: u64, + #[arg(long, default_value = DEFAULT_URL)] + url: String, + }, + + /// Proves membership and records an action, signed by a relay. + /// + /// The relay signs so the member never does. A member who signs their own + /// spend has published the link this pool exists to break. + Spend { + #[arg(long)] + program: String, + #[arg(long)] + note: PathBuf, + /// Who receives the payout. + #[arg(long)] + to: String, + /// The relay's keypair. Must not be the member's wallet. + #[arg(long)] + relay: String, + /// Taken out of the denomination, never added to it. + #[arg(long, default_value_t = 0)] + relay_fee: u64, + /// Call this program instead of paying the beneficiary directly. + #[arg(long)] + invoke: Option, + /// Instruction data for --invoke, hex. + #[arg(long, default_value = "")] + payload: String, + /// How many accounts the invoked instruction takes. Bound into the proof. + #[arg(long, default_value_t = 0)] + accounts: u8, + /// Hand the pool's vault to the callee as a signer, so the pool acts as + /// the authority. This is what a stake delegation needs. + #[arg(long)] + pool_signs: bool, + #[arg(long, default_value = DEFAULT_URL)] + url: String, + }, + + /// Executes every pending transfer in one transaction. Permissionless. + Settle { + #[arg(long)] + program: String, + #[arg(long)] + denomination: u64, + #[arg(long, default_value = DEFAULT_URL)] + url: String, + #[arg(long, default_value = DEFAULT_KEYPAIR)] + keypair: String, + }, +} + +fn parse_program(s: &str) -> Result { + s.parse() + .map_err(|e| anyhow::anyhow!("--program is not a pubkey: {e}")) } fn vk_digest(vk: &mirror_circuit::SolanaVerifyingKey) -> String { @@ -843,6 +958,104 @@ fn main() -> Result<()> { keypair, out, } => soak::run(&program, &url, &keypair, &out), + + Command::InitPool { + program, + denomination, + k_floor, + url, + keypair, + } => { + let chain = chain::Chain::new(&url); + let payer = soak::read_keypair(&keypair)?; + client::init_pool( + &chain, + &parse_program(&program)?, + denomination, + k_floor, + &payer, + ) + } + + Command::NoteNew { denomination, out } => client::note_new(denomination, &out), + + Command::Deposit { + program, + note, + url, + keypair, + } => { + let chain = chain::Chain::new(&url); + let depositor = soak::read_keypair(&keypair)?; + client::deposit(&chain, &parse_program(&program)?, ¬e, &depositor) + } + + Command::Tree { + program, + denomination, + url, + } => { + let chain = chain::Chain::new(&url); + client::tree(&chain, &parse_program(&program)?, denomination).map(|_| ()) + } + + Command::Spend { + program, + note, + to, + relay, + relay_fee, + invoke, + payload, + accounts, + pool_signs, + url, + } => { + let chain = chain::Chain::new(&url); + let relay = soak::read_keypair(&relay)?; + let beneficiary: solana_program::pubkey::Pubkey = to + .parse() + .map_err(|e| anyhow::anyhow!("--to is not a pubkey: {e}"))?; + let action = match invoke { + None => client::Action::Transfer, + Some(target) => client::Action::Invoke { + target: target + .parse() + .map_err(|e| anyhow::anyhow!("--invoke is not a pubkey: {e}"))?, + payload: hex::decode(payload.trim_start_matches("0x")) + .context("--payload is not hex")?, + accounts, + pool_signs, + }, + }; + client::spend( + &chain, + &parse_program(&program)?, + ¬e, + &beneficiary, + &relay, + relay_fee, + action, + ) + } + + Command::Settle { + program, + denomination, + url, + keypair, + } => { + let chain = chain::Chain::new(&url); + let settler = soak::read_keypair(&keypair)?; + let now = client::now()?; + client::settle( + &chain, + &parse_program(&program)?, + denomination, + &settler, + now, + ) + } Command::VerifySetup { seed, expect } => { let keys = mirror_circuit::generate_reproducible(seed.as_bytes()) .map_err(|e| anyhow::anyhow!("{e}"))?; diff --git a/crates/mirror-cli/src/note.rs b/crates/mirror-cli/src/note.rs new file mode 100644 index 00000000..a4a454f7 --- /dev/null +++ b/crates/mirror-cli/src/note.rs @@ -0,0 +1,196 @@ +//! A member's note, on disk. +//! +//! The secret is two field elements. Losing them loses the deposit — there is no +//! authority that can reissue a note, which is the same property that means no +//! authority can freeze one. So the file is deliberately boring: plain JSON, one +//! note per file, readable by anything, with the commitment stored alongside so +//! a member can check what they hold without a prover. +//! +//! It is not encrypted. Encrypting it would put a password between a member and +//! their funds and imply a security property this crate cannot deliver on its +//! own — the file is as sensitive as a keypair and wants the same handling. + +use anyhow::{anyhow, Context, Result}; +use mirror_core::{Field, Note}; +use serde::{Deserialize, Serialize}; +use std::path::Path; + +#[derive(Serialize, Deserialize)] +pub struct StoredNote { + /// The pool this note belongs to. A note is only spendable in the pool whose + /// denomination it was committed at. + pub denomination: u64, + /// Nullifier preimage, 32 bytes big-endian hex. + pub k: String, + /// Blinding factor, 32 bytes big-endian hex. + pub r: String, + /// Stored so a member can verify a deposit landed without running a prover. + pub commitment: String, +} + +impl StoredNote { + pub fn from_note(note: &Note, denomination: u64) -> Result { + Ok(StoredNote { + denomination, + k: hex::encode(note.k.to_bytes()), + r: hex::encode(note.r.to_bytes()), + commitment: hex::encode(note.commitment().map_err(|e| anyhow!("{e:?}"))?.to_bytes()), + }) + } + + pub fn note(&self) -> Result { + let k = field_from_hex(&self.k, "k")?; + let r = field_from_hex(&self.r, "r")?; + let note = Note::new(k, r, Field::from_u64(self.denomination)); + // A file whose commitment disagrees with its secrets has been edited or + // corrupted, and spending it would burn a nullifier for a leaf that is + // not in the tree. Caught here, where it is still only a bad file. + let recomputed = hex::encode(note.commitment().map_err(|e| anyhow!("{e:?}"))?.to_bytes()); + if recomputed != self.commitment.to_lowercase() { + return Err(anyhow!( + "this note file is inconsistent: its secrets commit to {recomputed}, \ + but the file records {}", + self.commitment + )); + } + Ok(note) + } + + pub fn commitment_bytes(&self) -> Result<[u8; 32]> { + let raw = hex::decode(&self.commitment).context("the commitment is not hex")?; + raw.try_into() + .map_err(|_| anyhow!("the commitment is not 32 bytes")) + } + + pub fn read(path: &Path) -> Result { + let text = std::fs::read_to_string(path) + .with_context(|| format!("reading the note at {}", path.display()))?; + serde_json::from_str(&text).with_context(|| format!("parsing {}", path.display())) + } + + /// Writes the note, refusing to overwrite. + /// + /// An overwrite here destroys a deposit, so the safe default is the only + /// default: there is no `--force`. + pub fn write(&self, path: &Path) -> Result<()> { + if path.exists() { + return Err(anyhow!( + "{} already exists. A note file is the only copy of a deposit's \ + secret, so this will not overwrite one — choose another path.", + path.display() + )); + } + let text = serde_json::to_string_pretty(self)?; + std::fs::write(path, text) + .with_context(|| format!("writing the note to {}", path.display()))?; + Ok(()) + } +} + +fn field_from_hex(s: &str, what: &str) -> Result { + let raw = hex::decode(s).with_context(|| format!("{what} is not hex"))?; + let arr: [u8; 32] = raw + .try_into() + .map_err(|_| anyhow!("{what} is not 32 bytes"))?; + Field::from_bytes(arr).map_err(|e| anyhow!("{what} is not a canonical field element: {e:?}")) +} + +/// Draws a fresh note from a CSPRNG seeded by the operating system. +/// +/// Rejection sampling rather than reduction: a reduced value would make some +/// field elements twice as likely as others, and while that is not exploitable +/// at this size it is free to avoid. A draw at or above the modulus is redrawn. +pub fn generate(denomination: u64) -> Result { + use ark_std::rand::{RngCore, SeedableRng}; + let mut rng = ark_std::rand::rngs::StdRng::from_entropy(); + let mut draw = || -> Field { + loop { + let mut bytes = [0u8; 32]; + rng.fill_bytes(&mut bytes); + // Clearing the top byte would bias the distribution; redrawing does + // not, and the expected number of redraws is under one in ten. + if let Ok(f) = Field::from_bytes(bytes) { + return f; + } + } + }; + Ok(Note::new(draw(), draw(), Field::from_u64(denomination))) +} + +#[cfg(test)] +mod tests { + use super::*; + + const DENOM: u64 = 20_000_019; + + #[test] + fn a_generated_note_round_trips_through_its_file_form() { + let note = generate(DENOM).unwrap(); + let stored = StoredNote::from_note(¬e, DENOM).unwrap(); + let text = serde_json::to_string(&stored).unwrap(); + let read: StoredNote = serde_json::from_str(&text).unwrap(); + assert_eq!(read.note().unwrap(), note); + } + + /// A note file whose commitment no longer matches its secrets must not be + /// spendable. Spending it would burn a nullifier against a leaf the tree + /// does not hold, which costs the member the deposit and tells them nothing + /// about why. + #[test] + fn a_note_whose_commitment_disagrees_with_its_secrets_is_refused() { + let note = generate(DENOM).unwrap(); + let mut stored = StoredNote::from_note(¬e, DENOM).unwrap(); + stored.commitment = hex::encode([0u8; 32]); + let err = stored.note().unwrap_err().to_string(); + assert!(err.contains("inconsistent"), "{err}"); + } + + /// The denomination is part of the commitment, so a file that claims the + /// wrong pool cannot silently be spent in it. + #[test] + fn changing_the_denomination_invalidates_the_note() { + let note = generate(DENOM).unwrap(); + let mut stored = StoredNote::from_note(¬e, DENOM).unwrap(); + stored.denomination = DENOM + 1; + assert!(stored.note().is_err()); + } + + #[test] + fn a_non_canonical_secret_is_refused_rather_than_reduced() { + let note = generate(DENOM).unwrap(); + let mut stored = StoredNote::from_note(¬e, DENOM).unwrap(); + stored.k = hex::encode([0xffu8; 32]); + assert!(stored.note().is_err()); + } + + /// Writing over an existing note destroys a deposit, so there is no path + /// that does it — not even an explicit one. + #[test] + fn writing_refuses_to_overwrite_an_existing_note() { + let dir = std::env::temp_dir().join(format!("mirror-note-test-{}", std::process::id())); + std::fs::create_dir_all(&dir).unwrap(); + let path = dir.join("note.json"); + let _ = std::fs::remove_file(&path); + + let first = StoredNote::from_note(&generate(DENOM).unwrap(), DENOM).unwrap(); + first.write(&path).unwrap(); + let second = StoredNote::from_note(&generate(DENOM).unwrap(), DENOM).unwrap(); + let err = second.write(&path).unwrap_err().to_string(); + assert!(err.contains("already exists"), "{err}"); + + // The file on disk is still the first note, not the second. + assert_eq!( + StoredNote::read(&path).unwrap().commitment, + first.commitment + ); + std::fs::remove_file(&path).unwrap(); + } + + #[test] + fn two_draws_differ() { + let a = generate(DENOM).unwrap(); + let b = generate(DENOM).unwrap(); + assert_ne!(a.k, b.k); + assert_ne!(a.r, b.r); + } +} diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs index 22ebab9f..ca6de3c0 100644 --- a/crates/mirror-cli/src/soak.rs +++ b/crates/mirror-cli/src/soak.rs @@ -793,7 +793,7 @@ pub fn keys() -> Result { generate_reproducible(crate::DEV_SETUP_SEED.as_bytes()).map_err(|e| anyhow!("{e}")) } -fn read_keypair(path: &str) -> Result { +pub fn read_keypair(path: &str) -> Result { let expanded = if let Some(rest) = path.strip_prefix("~/") { format!("{}/{rest}", std::env::var("HOME")?) } else { diff --git a/docs/USAGE.md b/docs/USAGE.md new file mode 100644 index 00000000..1760e01e --- /dev/null +++ b/docs/USAGE.md @@ -0,0 +1,222 @@ +# Using the pool + +Every command below was run against devnet to produce the output shown. Nothing +here is illustrative. + +The tool needs no server, no indexer and no account with anybody. It reads what +it needs from the chain each time, which is why a member can act from a machine +that has never seen this pool before, carrying nothing but their note file. + +``` +cargo build --release # the binary is target/release/mirror +``` + +Every command defaults to `--url https://api.devnet.solana.com` and to the Solana +CLI's own keypair at `~/.config/solana/id.json`, so the common case needs neither +flag. + +## The shape of it + +``` +note-new → deposit → ( wait for a crowd ) → spend → settle +``` + +A note is a secret you generate locally. Depositing it puts its *commitment* into +the pool's tree and escrows the denomination. Later you prove you own some note +in that tree — without saying which — and the pool acts. Settlement executes +everybody's action together. + +## 1. A pool + +One pool per denomination, globally, and anybody can create one. Splitting +deposits of the same size across two pools splits the anonymity set, and a split +set is worse for everyone in it. + +``` +$ mirror init-pool --program 8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa \ + --denomination 31000001 --k-floor 2 +pool DQ17r5reCu5P4efUzHQZq7Ye6D6vt72ThxatNdLBqcKt +vault 81odk482H6VpRQMk8tQaoThYr2ffJtugcJXjmKX2faco +signature 3Tbf4zMGDHW89bi3F7yhaGMAB6RqveDi5WTJAYKEe3Zr2F8eTAeT8zSTp6V3B4kWE4ZSXmpMdxpx6UayvdsiNNiw +``` + +`--k-floor` is the number of notes the pool must hold before it will act at all. +It bounds *program-visible* membership, which is all a program can check — see +`THREAT_MODEL.md` for what it does not bound. + +## 2. A note + +``` +$ mirror note-new --denomination 31000001 --out m1.json +wrote m1.json + denomination 31000001 + commitment 1ca8f208a35a6327c7871ee6d38268527260f53299c21b25133b32178989701c + +This file is the note. Treat it exactly as you would a keypair: +anyone holding it can spend the deposit, and losing it loses the +deposit with no way back. + +Next: mirror deposit --note m1.json +``` + +The file is plain JSON and unencrypted, deliberately. Encrypting it would put a +password between you and your funds and imply a security property this tool +cannot deliver by itself. Back it up the way you back up a keypair. + +It will not overwrite an existing note file, and there is no `--force`: an +overwrite here destroys a deposit. + +## 3. Deposit + +``` +$ mirror deposit --program 8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa --note m1.json +deposited 31000001 lamports into DQ17r5reCu5P4efUzHQZq7Ye6D6vt72ThxatNdLBqcKt +leaf 0 +signature JpCxFU4ipaBap15AWRxNVFoy8rskWZnUTFoBTLx1JBs5V2TKDCx7HhhzQ4AtYxL46yvfS8U1yXzSVLNg2au89hS +``` + +The deposit is public and it is signed by you. That is fine and unavoidable — +what the pool hides is not that you joined, but which member later acted. + +The escrowed amount is read from the pool, never from your instruction, so a +deposit cannot claim a size the pool did not set. + +## 4. Check the tree — optional, and worth doing once + +``` +$ mirror tree --program 8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa --denomination 31000001 +pool DQ17r5reCu5P4efUzHQZq7Ye6D6vt72ThxatNdLBqcKt +vault 81odk482H6VpRQMk8tQaoThYr2ffJtugcJXjmKX2faco +denomination 31000001 +k floor 2 +notes 2 deposited, 0 settled, 2 outstanding + +rebuilding the accumulator from chain history: + 3 transactions touched this pool + + leaves recovered 2 + pool reports 2 + rebuilt root 0c77cb909067c1a57811be8c05237aff2715c65c6250fdde764ed768096cd732 + on-chain root 0c77cb909067c1a57811be8c05237aff2715c65c6250fdde764ed768096cd732 + + the rebuilt tree matches the chain — proofs built from it will verify +``` + +This is the piece the rest stands on. The program stores only the accumulator's +*frontier* — one node per level — which is enough to append a leaf and produce a +root, and not enough to prove any particular leaf is in the tree. The leaves are +not lost: every commitment was an argument to a `Deposit` instruction, so the +whole set is in the transaction history in insertion order. + +Rebuilding it and finding the same root the program holds proves the recovered +set is complete and correctly ordered, which is exactly the precondition a +membership proof needs. `spend` does this rebuild itself; this command just lets +you watch it. + +## 5. Spend + +**The relay signs, never you.** A member who pays their own fee signs with their +own wallet, and `accountKeys[0]` is then the member — which publishes the link +the pool exists to break. Use a key that is not your wallet and has no history +with it. + +``` +$ mirror spend --program 8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa \ + --note m1.json --to BQ6piLqJD4CWn4V3VyDBh3RpU1FtsPh63P9y4wtwBxJg \ + --relay relay.json --relay-fee 100000 +rebuilding the accumulator so this note can be proved a member: + 3 transactions touched this pool + this note is leaf 0 of 2 +deriving the proving key from the published seed (this takes a moment) +proving membership + +submitted. The note is spent and the action is recorded. + nullifier 02218a1c2f8fe73a88e856ee9cf3c7e01508107490dca23a56af54166566d54e + record 59jcYijM67FYybt6MH36CU9YUMqQQ7Q5Lg6JiTXRXxfz + signature d5Y6CxbrNxu74u88AGT5NU1KqfLb4JgjtBXzT2TyXyvAGqJq2qgf5NftQwKoAH5TfHdh3fTQcbjkwudSDX8tj7U + +Nothing has been paid out yet — settlement executes the batch, which is +what gives every member's action one timestamp. Run `mirror settle`, or +wait for anyone else to. +``` + +The relay fee comes *out of* the denomination, never in addition to it, and a fee +at or above the denomination is refused on-chain. + +### Doing something other than paying + +The interesting case is not moving lamports. `--invoke` calls any program with +any payload: + +``` +mirror spend ... --invoke --payload --accounts +``` + +`--accounts` is how many accounts the call takes, and it is bound into the proof +so a settler cannot add or drop one. Which accounts fill those slots is chosen at +settlement — a real limit, stated in `THREAT_MODEL.md`. + +Add `--pool-signs` to hand the pool's vault to the callee as a **signer**, so the +pool acts as your authority rather than only as your funder. That is what a stake +delegation or a governance vote needs and a payment does not; `PROOF.md` has a +real stake delegation done this way on devnet. + +## 6. Settle + +Permissionless. Anyone can settle, so no operator's absence can strand you, and +you can always settle your own batch. + +Below the crowd floor it tells you what it is waiting for rather than failing +with an error code: + +``` +$ mirror settle --program 8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa --denomination 31000001 +looking for spends waiting to settle: + 4 transactions touched this pool + + 1 spend(s) pending, and this pool's floor is 2. + A batch below the floor may settle once every spend in it has waited 3600s; + the youngest has waited 16s, so 3584s remain. + + This is a liveness guarantee rather than a restriction: nobody can + hold a member's funds waiting for a crowd that never arrives. +``` + +Once the crowd is there: + +``` +$ mirror settle --program 8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa --denomination 31000001 +looking for spends waiting to settle: + 5 transactions touched this pool + +settled 2 spends in one transaction + signature 4EA7SszoGBWC1EKzgGn8XdLWvhMiWxp6v6Gbcmd7czzn7LfiUmoBvKwAJsM3XPt4Y2abm5P85M7w939ftRTvi6XC + +Every payout in that batch shares one timestamp and one ordering, which +is what stops arrival time from telling the members apart. +``` + +Both beneficiaries received `31000001 − 100000 = 30900001` lamports, in one +transaction, at one timestamp. + +`settle` executes plain transfers on its own. A pending CPI action is left alone +and reported, because the record binds *how many* accounts the call takes and +never *which*, so no tool can infer the account list its callee expects. + +## Escaping without a relay + +There is no `self-spend` command because none is needed. Relay for yourself with +`--relay-fee 0` and settle your own batch once the timeout passes. The cost is +the expected one — your own wallet signs, so you give up the anonymity the relay +path provides. It is an escape hatch, not a mode of operation, and it exists so +that nothing in the protocol can hold your escrow. + +## What can go wrong + +| | | +|---|---| +| `no pool exists at …` | Nobody has created that denomination yet. `init-pool` does, permissionlessly. | +| `this note is not a leaf of the pool` | The note was never deposited, or it belongs to a different denomination. | +| `this note file is inconsistent` | The file was edited or corrupted. Spending it would burn a nullifier against a leaf that is not in the tree, so it is refused first. | +| `the rebuilt root does not match the pool's` | The endpoint's history is incomplete — usually pruning. `mirror check-endpoint` tests for exactly that. | +| `this pool holds N notes and its floor is M` | The pool refuses to act below its floor. Wait for more members, or use a pool with a floor you can meet. | From 40e33fbfde12f645f13ea4b722cc7db7928664e5 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 19:31:44 -0300 Subject: [PATCH 62/83] measure: ten spends per settlement, and the packet is what stops the eleventh MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Scalable" was a word in this repository until now. The number is ten, and both sides of it are demonstrated rather than one. settled 10 spends in one transaction: 1228 bytes (4 to spare), 19545 CU of 200000 11 spends: rejected by the wire at 1327 bytes, 95 over the 1232-byte limit — while the SVM settled the same batch in 24158 CU, so compute is not the constraint The packet limit binds and compute is not close: a full batch spends under a tenth of the default instruction budget. Reporting only the failure at eleven would have left the reason ambiguous, so the test replays the identical eleven-spend batch into a second pool and watches the SVM settle it. If compute ever became the binding constraint, that assertion fails rather than the headline quietly becoming wrong. Two honest edges, both in the test rather than around it. Ten is the worst case — every spend brings three accounts no other spend shares, and a batch with a shared relay names fewer keys and fits more. And it is a ceiling per transaction, not per epoch: a busy pool settles in several batches and pays for it in timestamps, which is a real cost to the anonymity and the reason to publish the number. Also refuses the mistake that undoes the protocol. `spend` now reads the pool's depositors out of the same scan it was already doing and refuses to relay through a key that deposited. The relay signs, so its key is public on the spend; a key on both sides links them and this action's anonymity set collapses to one. It is the easy mistake, because the obvious key to hand --relay is the one already funded — the one that deposited. Verified against devnet by making it. 209 tests. --- README.md | 30 +- crates/mirror-cli/src/client.rs | 21 + crates/mirror-cli/src/history.rs | 23 +- docs/USAGE.md | 27 ++ programs/mirror-pool/tests/batch_ceiling.rs | 496 ++++++++++++++++++++ 5 files changed, 595 insertions(+), 2 deletions(-) create mode 100644 programs/mirror-pool/tests/batch_ceiling.rs diff --git a/README.md b/README.md index d62a12c7..e12c0500 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ not say. There is a section below of things we deliberately do not claim. | `crates/mirror-provenance` | The funding-provenance measurement. | | `crates/mirror-cli` | The tool. `init-pool`, `note-new`, `deposit`, `tree`, `spend`, `settle` for members; `setup`, `verify-setup`, `soak` for operators; `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection` for the measurement. | -**198 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**209 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -121,6 +121,34 @@ The relay signs and the member never does, so no member key appears on chain after the deposit. Below the crowd floor, `settle` says what it is waiting for and why rather than returning an error code. +### How large a crowd fits in one settlement + +Ten. Measured, not estimated: + +``` +settled 10 spends in one transaction: 1228 bytes (4 to spare), 19545 CU of 200000 +11 spends: rejected by the wire at 1327 bytes, 95 over the 1232-byte limit — +while the SVM settled the same batch in 24158 CU, so compute is not the constraint +``` + +The **packet size** binds and compute is not close: a full ten-spend settlement +uses under 10% of the default instruction budget. Each spend brings three +accounts nobody else shares — its record, its beneficiary, its relay — so the +transaction grows by about 99 bytes a member while the compute grows by about +460 CU. + +`ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_the_eleventh` +pins both numbers and demonstrates the limit from both sides: it measures the +eleventh batch at 1327 bytes *and* replays the identical batch into a second +pool, where the SVM settles it without complaint. If compute ever became the +binding constraint, that test fails rather than quietly reporting the wrong +reason. + +That ten is a worst case, and the test says so: a batch whose members shared a +relay would name fewer distinct keys and fit more. It is also a ceiling per +transaction, not per epoch — settlement is permissionless and a busy pool +settles in several batches, at the cost of several timestamps rather than one. + ## Synchronised actions are the point The brief asks for an anonymity set for *behaviour*, not for funds. That diff --git a/crates/mirror-cli/src/client.rs b/crates/mirror-cli/src/client.rs index e130f52c..108ea21c 100644 --- a/crates/mirror-cli/src/client.rs +++ b/crates/mirror-cli/src/client.rs @@ -335,6 +335,27 @@ pub fn spend( history.commitments.len() ); + // The mistake that undoes the whole protocol, caught before it lands. + // + // The relay signs, so its key is public on the spend transaction. If that + // same key signed a deposit into this pool, an observer joins the two by + // reading two public transactions and the anonymity set collapses to one for + // this action — the proof stays sound and protects nothing. It is an easy + // mistake to make, because the obvious key to hand `--relay` is the one + // already funded, which is the one that deposited. + if history.depositors.contains(&relay.pubkey()) { + return Err(anyhow!( + "the relay {} has also deposited into this pool.\n\n\ + The relay signs this spend, so its key is public on it. A key that \ + appears on both a deposit and a spend links them, and this action's \ + anonymity set collapses to one — the proof would be sound and \ + pointless.\n\n\ + Use a key with no history with this pool, funded from somewhere that \ + does not lead back to your deposit.", + relay.pubkey() + )); + } + let binding = mirror_core::action_binding( action.selector(), &action.target(), diff --git a/crates/mirror-cli/src/history.rs b/crates/mirror-cli/src/history.rs index f11f9d1b..4e548ba6 100644 --- a/crates/mirror-cli/src/history.rs +++ b/crates/mirror-cli/src/history.rs @@ -36,6 +36,13 @@ pub struct History { /// Note commitments in insertion order. Index `i` is leaf `i`. pub commitments: Vec<[u8; 32]>, pub spends: Vec, + /// Every key that has signed a deposit into this pool. + /// + /// Collected so the tool can catch the one mistake that undoes everything: + /// relaying a spend with a key that also deposited. Depositors are public by + /// necessity — a deposit is signed — so reading them costs nothing beyond + /// the scan already being done. + pub depositors: Vec, } impl History { @@ -73,6 +80,7 @@ pub fn scan(chain: &Chain, program_id: &Pubkey, pool: &Pubkey, verbose: bool) -> let mut commitments = Vec::new(); let mut spends = Vec::new(); + let mut depositors: Vec = Vec::new(); for (n, signature) in signatures.iter().enumerate() { if verbose && n > 0 && n % 25 == 0 { @@ -108,7 +116,14 @@ pub fn scan(chain: &Chain, program_id: &Pubkey, pool: &Pubkey, verbose: bool) -> continue; }; match decoded { - MirrorIx::Deposit { commitment } => commitments.push(commitment), + MirrorIx::Deposit { commitment } => { + commitments.push(commitment); + if let Some(who) = ix.accounts.first().and_then(|i| keys.get(*i as usize)) { + if !depositors.contains(who) { + depositors.push(*who); + } + } + } MirrorIx::SubmitSpend { nullifier, .. } => { let (spend, _) = spend_address(program_id, pool, &nullifier); spends.push(SubmittedSpend { nullifier, spend }); @@ -121,6 +136,7 @@ pub fn scan(chain: &Chain, program_id: &Pubkey, pool: &Pubkey, verbose: bool) -> Ok(History { commitments, spends, + depositors, }) } @@ -146,6 +162,7 @@ mod tests { let history = History { commitments: commitments(9), spends: Vec::new(), + depositors: Vec::new(), }; let mut frontier = Frontier::new().unwrap(); for c in &history.commitments { @@ -159,12 +176,14 @@ mod tests { let forward = History { commitments: commitments(5), spends: Vec::new(), + depositors: Vec::new(), }; let mut backward = forward.commitments.clone(); backward.reverse(); let reversed = History { commitments: backward, spends: Vec::new(), + depositors: Vec::new(), }; assert_ne!( forward.tree().unwrap().root().unwrap(), @@ -179,6 +198,7 @@ mod tests { let history = History { commitments: commitments(6), spends: Vec::new(), + depositors: Vec::new(), }; for (i, c) in history.commitments.clone().iter().enumerate() { assert_eq!(history.index_of(c), Some(i as u64)); @@ -193,6 +213,7 @@ mod tests { let history = History { commitments: vec![[0xff; 32]], spends: Vec::new(), + depositors: Vec::new(), }; let err = history.tree().unwrap_err().to_string(); assert!(err.contains("canonical"), "{err}"); diff --git a/docs/USAGE.md b/docs/USAGE.md index 1760e01e..15af8d21 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -120,6 +120,14 @@ own wallet, and `accountKeys[0]` is then the member — which publishes the link the pool exists to break. Use a key that is not your wallet and has no history with it. +The tool refuses outright if the relay you name has also deposited into this +pool, because that is the same mistake wearing a different key: the deposit and +the spend would both carry it, an observer joins them by reading two public +transactions, and the proof stays sound while protecting nothing. + +Funding the relay matters too, and no tool can check it for you. A relay topped +up from the wallet that deposited leads back to it in one hop. + ``` $ mirror spend --program 8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa \ --note m1.json --to BQ6piLqJD4CWn4V3VyDBh3RpU1FtsPh63P9y4wtwBxJg \ @@ -203,6 +211,24 @@ transaction, at one timestamp. and reported, because the record binds *how many* accounts the call takes and never *which*, so no tool can infer the account list its callee expects. +## How many members settle together + +Ten spends fit in one settlement transaction. The limit is the 1232-byte packet, +not compute: ten uses 1228 bytes and 19,545 of the 200,000 compute units a single +instruction gets by default. + +Each spend brings three accounts nobody else shares — its record, its beneficiary +and its relay — so the transaction grows about 99 bytes per member. A batch whose +members shared a relay names fewer distinct keys and fits more. + +This is a ceiling per *transaction*, not per epoch. Settlement is permissionless, +so a pool with thirty pending spends settles in three batches; the cost is three +timestamps rather than one, which is a real cost to the anonymity and the reason +the number is worth knowing. + +`ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_the_eleventh` +pins it. + ## Escaping without a relay There is no `self-spend` command because none is needed. Relay for yourself with @@ -220,3 +246,4 @@ that nothing in the protocol can hold your escrow. | `this note file is inconsistent` | The file was edited or corrupted. Spending it would burn a nullifier against a leaf that is not in the tree, so it is refused first. | | `the rebuilt root does not match the pool's` | The endpoint's history is incomplete — usually pruning. `mirror check-endpoint` tests for exactly that. | | `this pool holds N notes and its floor is M` | The pool refuses to act below its floor. Wait for more members, or use a pool with a floor you can meet. | +| `the relay … has also deposited into this pool` | The worst mistake available, refused rather than warned about. The relay signs the spend, so a key that also signed a deposit links the two and this action's anonymity set collapses to one. Use a key with no history with the pool. | diff --git a/programs/mirror-pool/tests/batch_ceiling.rs b/programs/mirror-pool/tests/batch_ceiling.rs new file mode 100644 index 00000000..bb147e3e --- /dev/null +++ b/programs/mirror-pool/tests/batch_ceiling.rs @@ -0,0 +1,496 @@ +//! How many spends actually fit in one settlement, measured against the compiled +//! program. +//! +//! `settle_epoch` takes a count and reads three accounts per spend, and every +//! sentence in this repository that says "batch" is quietly assuming that count +//! can be large. Two unrelated things bound it: the 1232-byte packet a +//! transaction has to fit inside, and the compute budget the instruction runs +//! under. Only one of them binds, and which one it is changes what a settler +//! should do about it — ask for more compute, or send more transactions. +//! +//! So the number is taken rather than argued. Nothing below is extrapolated from +//! a smaller batch: the ceiling is found by settling real spends against the real +//! `.so` in litesvm, and both limits are reported at the answer. +//! +//! Requires `make build-sbf` first, like the end-to-end suite. This is a +//! measurement of the deployed artefact, not of the host crate, so a missing +//! artefact says so rather than quietly measuring nothing. + +use litesvm::LiteSVM; +use mirror_circuit::{generate_reproducible, prove, Keys, SolanaProof, Witness}; +use mirror_core::{Field, MerkleTree, Note}; +use mirror_pool_program::{ + instruction::Instruction as MirrorIx, + pda::{pool_address, spend_address, vault_address}, + Pool, +}; +use solana_keypair::Keypair; +use solana_message::Message; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use solana_signer::Signer; +use solana_transaction::Transaction; + +const SEED: &[u8] = b"mirror-pool-reproducible-dev-setup-v1"; +const DENOMINATION: u64 = 100_000_000; // 0.1 SOL +const ENTRY_FEE: u64 = 0; +const K_FLOOR: u32 = 4; +const RELAY_FEE: u64 = 1_000_000; +const SELECTOR: u64 = mirror_pool_program::processor::SELECTOR_TRANSFER; + +/// The 1280-byte IPv6 minimum MTU, less a 40-byte IPv6 header and an 8-byte +/// fragment header. Restated here because no crate this test already depends on +/// re-exports it; `solana_packet::PACKET_DATA_SIZE` is the original, and the +/// derivation is written out so a reader can check the number rather than trust +/// it. +const PACKET_DATA_SIZE: usize = 1280 - 40 - 8; + +/// What a transaction carrying a single non-budget instruction is given unless it +/// asks for more. Asking costs a second instruction, and a second instruction +/// costs bytes this measurement has none to spare — which is the whole reason the +/// two limits have to be reported together rather than one at a time. +const DEFAULT_COMPUTE_BUDGET: u64 = 200_000; + +// --------------------------------------------------------------------------- +// The answers, pinned +// +// Produced by the test at the bottom of this file and written back here, so that +// a change to the account shape of `settle_epoch` — a fourth account per spend, +// a signer added to the batch — fails a test instead of silently halving what a +// settler can carry. +// --------------------------------------------------------------------------- + +/// The largest batch that both fits in a packet and executes. +const MAX_SPENDS_PER_SETTLEMENT: usize = 10; +/// The wire size of that settlement. There is no room for an eleventh spend and +/// not much room for anything else either. +const BYTES_AT_MAX: usize = 1228; + +fn program_bytes() -> Vec { + let path = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../target/sbf/deploy/mirror_pool_program.so" + ); + std::fs::read(path).unwrap_or_else(|e| { + panic!("could not read {path}: {e}\n\nRun `make build-sbf` first."); + }) +} + +struct Env { + svm: LiteSVM, + program_id: Pubkey, + payer: Keypair, + pool: Pubkey, + vault: Pubkey, +} + +fn setup() -> Env { + let mut svm = LiteSVM::new(); + let program_id = Pubkey::new_unique(); + svm.add_program(program_id, &program_bytes()) + .expect("loading the built program"); + + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), 100_000_000_000).unwrap(); + + let (pool, _) = pool_address(&program_id, DENOMINATION); + let (vault, _) = vault_address(&program_id, &pool); + + Env { + svm, + program_id, + payer, + pool, + vault, + } +} + +impl Env { + fn send(&mut self, ix: Instruction, signer: &Keypair) -> Result<(), String> { + let msg = Message::new(&[ix], Some(&signer.pubkey())); + let tx = Transaction::new(&[signer], msg, self.svm.latest_blockhash()); + self.svm + .send_transaction(tx) + .map(|_| ()) + .map_err(|e| format!("{:?}", e.err)) + } + + fn init_pool(&mut self) { + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::InitPool { + denomination: DENOMINATION, + entry_fee: ENTRY_FEE, + k_floor: K_FLOOR, + } + .pack(), + vec![ + AccountMeta::new(self.payer.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(self.vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let payer = self.payer.insecure_clone(); + self.send(ix, &payer).expect("init_pool"); + } + + fn deposit(&mut self, commitment: Field, depositor: &Keypair) -> Result<(), String> { + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::Deposit { + commitment: commitment.to_bytes(), + } + .pack(), + vec![ + AccountMeta::new(depositor.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(self.vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + self.send(ix, depositor) + } + + fn spend_count(&self) -> u64 { + let mut data = self.svm.get_account(&self.pool).expect("pool exists").data; + Pool::load(&mut data).unwrap().spend_count() + } +} + +/// One spend as settlement sees it: the record, the beneficiary the proof bound, +/// and the relay that submitted it. Three accounts, which is the unit this whole +/// measurement is denominated in. +type Party = (Pubkey, Pubkey, Pubkey); + +fn spend_ix( + env: &Env, + proof: &SolanaProof, + nullifier: [u8; 32], + beneficiary: &Pubkey, + relay: &Pubkey, +) -> Instruction { + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR, + target_program: [0u8; 32], + beneficiary: beneficiary.to_bytes(), + relay_fee: RELAY_FEE, + action_accounts: 0, + payload: Vec::new(), + } + .pack(), + vec![ + AccountMeta::new(*relay, true), + AccountMeta::new(env.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ) +} + +fn settle_ix(env: &Env, batch: &[Party], settler: &Pubkey) -> Instruction { + let mut metas = vec![ + AccountMeta::new(*settler, true), + AccountMeta::new(env.pool, false), + AccountMeta::new(env.vault, false), + ]; + for (spend, beneficiary, relay) in batch { + metas.push(AccountMeta::new(*spend, false)); + metas.push(AccountMeta::new(*beneficiary, false)); + metas.push(AccountMeta::new(*relay, false)); + } + Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SettleEpoch { + count: batch.len() as u8, + } + .pack(), + metas, + ) +} + +fn settle_tx(env: &Env, batch: &[Party], settler: &Keypair) -> Transaction { + let ix = settle_ix(env, batch, &settler.pubkey()); + let msg = Message::new(&[ix], Some(&settler.pubkey())); + Transaction::new(&[settler], msg, env.svm.latest_blockhash()) +} + +/// What this transaction weighs on the wire. +/// +/// A legacy transaction is a compact array of 64-byte signatures followed by the +/// serialized message, and `Message::serialize` is the encoding a validator +/// receives, not an approximation of it. The one-byte length prefix is asserted +/// rather than assumed: a second signer would move the answer by 65 bytes without +/// changing anything visible at the call site. +fn wire_len(tx: &Transaction) -> usize { + assert_eq!( + tx.signatures.len(), + 1, + "the framing here holds for a single signature" + ); + 1 + 64 * tx.signatures.len() + tx.message.serialize().len() +} + +/// The largest batch whose transaction still fits in a packet, found by +/// serializing rather than by arithmetic. +/// +/// Placeholder keys are legitimate here and only here: transaction size depends +/// on how many distinct accounts the message names, not on which. The real batch +/// is checked against this bracket below, so an error in that reasoning fails the +/// test rather than biasing it. +fn largest_batch_that_fits(env: &Env, settler: &Keypair) -> usize { + let mut fits = 0; + for n in 1..=64 { + let placeholders: Vec = (0..n) + .map(|_| { + ( + Pubkey::new_unique(), + Pubkey::new_unique(), + Pubkey::new_unique(), + ) + }) + .collect(); + if wire_len(&settle_tx(env, &placeholders, settler)) <= PACKET_DATA_SIZE { + fits = n; + } + } + fits +} + +/// A member's proof and the two addresses it was bound to. +/// +/// The binding covers the selector, the target, the beneficiary, the relay fee +/// and the payload — not the pool and not the relay's identity. So one round of +/// proving can be replayed into a second, identical pool, which is what lets this +/// file run two independent settlement experiments for the price of one. +struct Ticket { + proof: SolanaProof, + beneficiary: Pubkey, + relay: Keypair, +} + +fn host_tree(count: usize) -> (MerkleTree, Vec) { + let denom_tag = Field::from_u64(DENOMINATION); + let mut tree = MerkleTree::new().unwrap(); + let mut notes = Vec::new(); + for i in 1..=count as u64 { + let note = Note::new( + Field::from_u64(i * 1_000_003), + Field::from_u64(i * 7_919), + denom_tag, + ); + tree.insert(note.commitment().unwrap()).unwrap(); + notes.push(note); + } + (tree, notes) +} + +fn prove_tickets(keys: &Keys, tree: &MerkleTree, notes: &[Note]) -> Vec { + use ark_std::rand::SeedableRng; + notes + .iter() + .enumerate() + .map(|(index, note)| { + let beneficiary = Pubkey::new_unique(); + let merkle_proof = tree.proof(index as u64).unwrap(); + let binding = mirror_core::action_binding( + SELECTOR, + &[0u8; 32], + &beneficiary.to_bytes(), + RELAY_FEE, + 0, + &[], + ); + let witness = Witness { + note: *note, + merkle_proof: &merkle_proof, + root: tree.root().unwrap(), + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([index as u8; 32]); + Ticket { + proof: prove(keys, &witness, &mut rng).expect("proving"), + beneficiary, + relay: Keypair::new(), + } + }) + .collect() +} + +/// A fresh pool holding `notes`, with every ticket submitted and pending. +fn pending_pool(notes: &[Note], tickets: &[Ticket]) -> (Env, Vec) { + let mut env = setup(); + env.init_pool(); + + for (i, note) in notes.iter().enumerate() { + let depositor = Keypair::new(); + env.svm + .airdrop(&depositor.pubkey(), DENOMINATION + 10_000_000) + .unwrap(); + env.deposit(note.commitment().unwrap(), &depositor) + .unwrap_or_else(|e| panic!("deposit {i} failed: {e}")); + } + + let mut pending = Vec::new(); + for (i, ticket) in tickets.iter().enumerate() { + env.svm + .airdrop(&ticket.relay.pubkey(), 10_000_000_000) + .unwrap(); + let nullifier = ticket.proof.public_inputs[1]; + let ix = spend_ix( + &env, + &ticket.proof, + nullifier, + &ticket.beneficiary, + &ticket.relay.pubkey(), + ); + let relay = ticket.relay.insecure_clone(); + env.send(ix, &relay) + .unwrap_or_else(|e| panic!("submit {i} failed: {e}")); + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + pending.push((spend_pda, ticket.beneficiary, ticket.relay.pubkey())); + } + (env, pending) +} + +/// The measurement. +/// +/// Bracketing, because a Groth16 proof takes about four seconds and sweeping +/// upward from one spend would spend a minute rediscovering that small batches +/// are small. Transaction size is a pure function of the account count, so the +/// region worth proving for is found first by serializing placeholder batches — +/// free, and cross-checked against the real batch inside the sweep. Only then are +/// proofs made, one more than the bracket admits, and the real sweep runs +/// *downward* from the bracket: a settlement that is rejected leaves its records +/// pending, so one pool can be asked many times, but a settlement that succeeds +/// consumes them, so the first success has to be the answer. +/// +/// Every spend here brings three accounts no other spend shares. That is the +/// worst case for the encoding and it is the case a settler has to plan against; +/// a batch whose members happened to share a relay would name fewer distinct keys +/// and is a different measurement. +#[test] +fn ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_the_eleventh() { + let probe = setup(); + let prober = Keypair::new(); + let bracket = largest_batch_that_fits(&probe, &prober); + println!("the encoding admits {bracket} spends; proving one more than that"); + + let keys = generate_reproducible(SEED).expect("setup"); + let (tree, notes) = host_tree(bracket + 1); + let tickets = prove_tickets(&keys, &tree, ¬es); + + let (mut env, pending) = pending_pool(¬es, &tickets); + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + + let mut ceiling = 0usize; + let mut bytes_at_ceiling = 0usize; + let mut cu_at_ceiling = 0u64; + for n in (1..=bracket).rev() { + let tx = settle_tx(&env, &pending[..n], &settler); + let bytes = wire_len(&tx); + assert!( + bytes <= PACKET_DATA_SIZE, + "the placeholder bracket disagreed with a real batch of {n}: {bytes} bytes" + ); + match env.svm.send_transaction(tx) { + Ok(meta) => { + ceiling = n; + bytes_at_ceiling = bytes; + cu_at_ceiling = meta.compute_units_consumed; + break; + } + Err(e) => println!("{n} spends fit in {bytes} bytes but failed: {:?}", e.err), + } + } + assert!(ceiling > 0, "no batch settled at all"); + assert_eq!( + env.spend_count(), + ceiling as u64, + "the settlement that succeeded did not settle the whole batch" + ); + println!( + "settled {ceiling} spends in one transaction: {bytes_at_ceiling} bytes \ + ({} to spare), {cu_at_ceiling} CU of {DEFAULT_COMPUTE_BUDGET}", + PACKET_DATA_SIZE - bytes_at_ceiling + ); + + // Why one more fails, demonstrated rather than asserted — and it needs two + // judges, because neither can answer alone. litesvm never sees a packet, so + // it will happily execute a batch no validator would accept; the wire format + // never sees a compute meter. So the same batch of `ceiling + 1` is measured + // on the wire and replayed into a second, identical pool where the SVM gets + // to rule on it. If the SVM settles it while the bytes are over the limit, + // the packet is what binds and compute is not close. + let over = ceiling + 1; + let (mut second, again) = pending_pool(¬es, &tickets); + let other = Keypair::new(); + second.svm.airdrop(&other.pubkey(), 10_000_000_000).unwrap(); + let tx = settle_tx(&second, &again[..over], &other); + let bytes_over = wire_len(&tx); + let verdict = second.svm.send_transaction(tx); + + match (&verdict, bytes_over > PACKET_DATA_SIZE) { + (Ok(meta), true) => println!( + "{over} spends: rejected by the wire at {bytes_over} bytes, \ + {} over the {PACKET_DATA_SIZE}-byte limit — while the SVM settled the \ + same batch in {} CU, so compute is not the constraint", + bytes_over - PACKET_DATA_SIZE, + meta.compute_units_consumed + ), + (Err(e), _) => println!( + "{over} spends: rejected by the runtime at {bytes_over} bytes: {:?}", + e.err + ), + (Ok(_), false) => panic!( + "a batch of {over} both fits in {bytes_over} bytes and executes, so the \ + sweep above stopped one short of the real ceiling" + ), + } + + assert!( + bytes_over > PACKET_DATA_SIZE, + "{over} spends fit in {bytes_over} bytes, so the packet is not what stops them" + ); + // Both halves of the claim are load-bearing. If the SVM ever refuses this + // batch, the packet stops being the only thing in the way and the name of + // this test is no longer true, whatever the ceiling turns out to be. + let Ok(over_meta) = &verdict else { + panic!( + "the SVM refused {over} spends, so the packet limit is not what binds \ + and this test reports the wrong constraint" + ) + }; + assert!( + over_meta.compute_units_consumed < DEFAULT_COMPUTE_BUDGET, + "settling {over} spends cost {} CU, at or over the default budget, so \ + compute now binds alongside the packet", + over_meta.compute_units_consumed + ); + + assert_eq!( + ceiling, MAX_SPENDS_PER_SETTLEMENT, + "the settlement ceiling moved: {ceiling} spends per transaction, not \ + {MAX_SPENDS_PER_SETTLEMENT}" + ); + assert_eq!( + bytes_at_ceiling, BYTES_AT_MAX, + "the wire size of a full settlement moved: {bytes_at_ceiling} bytes, not \ + {BYTES_AT_MAX}" + ); + assert!( + cu_at_ceiling < DEFAULT_COMPUTE_BUDGET, + "a full settlement used {cu_at_ceiling} CU, over the default budget" + ); +} From 0bcb1779c8e0f88760b9c9eba06b117f0dd1d2a2 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 22:17:59 -0300 Subject: [PATCH 63/83] chore: run state is not deliverable, and it was being published The note ledgers of past soak runs were tracked. They are one operator's state against one cluster, they mean nothing to a reader of this repository, and each one holds the secrets that release its pool's escrow. Untracked, and ignored along with the validator-choice plans the crowd runs write. --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 7fcdd2e2..a926fa12 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,10 @@ keys/ *.keypair.json notes/ sp4/ + +# Live-run state, not deliverable. The note ledgers hold the secrets that +# release a pool's escrow, and the plan files record which validator each +# member of a run asked for. Both belong to one operator's run against one +# cluster, and neither means anything to a reader of this repository. +data/*-notes*.json +data/crowd-plan-*.json From abe00f092ba04830ebd843a21b6f93369d972f51 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Sun, 26 Jul 2026 22:18:14 -0300 Subject: [PATCH 64/83] measure: a crowd that diverges is a smaller crowd, and it costs a member MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PROOF.md shows the pool delegating stake as a member's authority once, in a batch whose other members were paying. That is the easy case for an anonymity set: nothing told the members apart to begin with. The question left open was what happens when the actions diverge in content — when each member picks their own validator, which is the behavioural pattern that actually fingerprints a staker across epochs. `mirror crowd` settles six delegations to six different devnet validators in one transaction and reads every stake account back off the cluster, because the proof binds how many accounts a call takes and never which: before settlement nothing on chain says which validator member 3 wanted. The read-back is the only check that limit can have. The cost is measured rather than argued. A member who picks their own validator names a vote account nobody else names — one extra distinct key per spend — so the batch loses a member: seven if the crowd agrees, six if it does not. Taken twice by paths that share no code, litesvm in action_ceiling.rs and the live run, both landing on 1194 bytes. Two things this turned up that were not expected: Compute is not the afterthought it is for payments. Ten transfers settle in 19,545 CU; six delegations took 142,856 of the 200,000 a single instruction gets. At that ceiling both exits are shut — asking for a larger budget costs a second instruction worth 40 bytes, and the batch has 38 to spare. And the run has to survive its own network. A public endpoint timed out mid-confirmation with six deposits already made, which under the old rules meant escrow nobody could release. Resuming now rebuilds from the chain rather than from a file: a note's nullifier fixes its record's address, and the record carries the accounts settlement must name. Also extracts stake.rs, because the DelegateStake layouts were about to have a second copy, and persists each run's results so that fixing a sentence in a generated report does not cost another live run. --- README.md | 56 +- crates/mirror-cli/src/chain.rs | 57 + crates/mirror-cli/src/crowd.rs | 1073 ++++++++++++++++++ crates/mirror-cli/src/main.rs | 38 + crates/mirror-cli/src/soak.rs | 138 +-- crates/mirror-cli/src/stake.rs | 221 ++++ data/crowd-result-43000007.json | 149 +++ docs/CROWD.md | 85 ++ docs/PROOF.md | 2 +- docs/USAGE.md | 56 +- programs/mirror-pool/tests/action_ceiling.rs | 1041 +++++++++++++++++ 11 files changed, 2789 insertions(+), 127 deletions(-) create mode 100644 crates/mirror-cli/src/crowd.rs create mode 100644 crates/mirror-cli/src/stake.rs create mode 100644 data/crowd-result-43000007.json create mode 100644 docs/CROWD.md create mode 100644 programs/mirror-pool/tests/action_ceiling.rs diff --git a/README.md b/README.md index e12c0500..2a9a2143 100644 --- a/README.md +++ b/README.md @@ -79,9 +79,9 @@ not say. There is a section below of things we deliberately do not claim. | `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain. | | `crates/mirror-circuit` | R1CS gadget, membership circuit, prover, key export. | | `crates/mirror-provenance` | The funding-provenance measurement. | -| `crates/mirror-cli` | The tool. `init-pool`, `note-new`, `deposit`, `tree`, `spend`, `settle` for members; `setup`, `verify-setup`, `soak` for operators; `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection` for the measurement. | +| `crates/mirror-cli` | The tool. `init-pool`, `note-new`, `deposit`, `tree`, `spend`, `settle` for members; `setup`, `verify-setup`, `soak`, `crowd` for operators; `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection` for the measurement. | -**209 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**216 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -123,7 +123,14 @@ and why rather than returning an error code. ### How large a crowd fits in one settlement -Ten. Measured, not estimated: +It depends on what the crowd is doing, and the answer is measured in every case +rather than estimated: + +| the batch | members per settlement | bytes | taken | +|---|---|---|---| +| plain payments | **10** | 1228 | settled in litesvm | +| stake delegations, everyone to the same validator | **7** | 1140 | settled in litesvm | +| stake delegations, a different validator each | **6** | 1194 | settled in litesvm **and on devnet** | ``` settled 10 spends in one transaction: 1228 bytes (4 to spare), 19545 CU of 200000 @@ -131,21 +138,37 @@ settled 10 spends in one transaction: 1228 bytes (4 to spare), 19545 CU of 20000 while the SVM settled the same batch in 24158 CU, so compute is not the constraint ``` -The **packet size** binds and compute is not close: a full ten-spend settlement -uses under 10% of the default instruction budget. Each spend brings three -accounts nobody else shares — its record, its beneficiary, its relay — so the -transaction grows by about 99 bytes a member while the compute grows by about -460 CU. +The **packet size** binds in all three. Each spend brings accounts nobody else +shares — its record, its beneficiary, its relay — so a payment costs about 99 +bytes a member, and a call costs more because it also names its callee and the +callee's accounts. + +The interesting row is the last one. **Actions that diverge in content cost +anonymity-set size**: a member who picks their own validator names a vote +account nobody else in the batch names, one extra key per spend, and the batch +loses a member. That is a real trade-off in the design, so it is measured from +two sides that share no code path — `a_crowd_that_agrees_on_its_validator_carries_one_more_member` +in litesvm and a live devnet run in [`docs/CROWD.md`](docs/CROWD.md) — and both +land on 1194 bytes. + +Compute is not close for payments: ten settle in under 10% of the default +instruction budget. It is much closer for delegations. On devnet the six-member +divergent batch burned 142,856 of the 200,000 CU a single instruction gets, and +`CROWD.md` reports where that leaves a settler: **at that ceiling both exits are +shut**. Asking for a larger compute budget costs a second instruction, measured +at 40 bytes against that very batch, and the batch has 38 to spare. Raising the +budget means dropping a member. `ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_the_eleventh` -pins both numbers and demonstrates the limit from both sides: it measures the -eleventh batch at 1327 bytes *and* replays the identical batch into a second -pool, where the SVM settles it without complaint. If compute ever became the -binding constraint, that test fails rather than quietly reporting the wrong -reason. - -That ten is a worst case, and the test says so: a batch whose members shared a -relay would name fewer distinct keys and fit more. It is also a ceiling per +demonstrates the payment limit from both sides: it measures the eleventh batch +at 1327 bytes *and* replays the identical batch into a second pool, where the +SVM settles it without complaint. If compute ever became the binding constraint, +that test fails rather than quietly reporting the wrong reason. +`every_account_an_action_names_costs_the_batch_a_member` does the same for the +two delegation rows, against the real Stake program. + +Every figure is a worst case, and the tests say so: a batch whose members shared +a relay would name fewer distinct keys and fit more. They are also ceilings per transaction, not per epoch — settlement is permissionless and a busy pool settles in several batches, at the cost of several timestamps rather than one. @@ -540,5 +563,6 @@ that keeps only its successful runs is selecting rather than reporting. | `docs/MEASUREMENT_LOG.md` | Every run. | | `docs/THREAT_MODEL.md` | The adversary, what holds, and every place it stops. | | `docs/PROOF.md` | Devnet signatures for every flow, and the rejections. | +| `docs/CROWD.md` | Six members delegating to six different validators in one devnet transaction, and what divergence costs. | | `docs/USAGE.md` | Every command, with real output. | | `docs/PLAN.md` | The design as decided, and where the shipped protocol departs from it. | diff --git a/crates/mirror-cli/src/chain.rs b/crates/mirror-cli/src/chain.rs index 886f327d..3a5009fe 100644 --- a/crates/mirror-cli/src/chain.rs +++ b/crates/mirror-cli/src/chain.rs @@ -293,4 +293,61 @@ impl Chain { v.as_u64() .ok_or_else(|| anyhow!("rent-exempt minimum missing")) } + + /// Vote accounts of validators the cluster currently counts as active, + /// ordered by stake, largest first. + /// + /// Only the `current` list is read. A delinquent validator's vote account + /// still exists and `DelegateStake` would still accept it, so a run that + /// drew from `delinquent` would succeed and prove the same thing — but the + /// claim being made is about members choosing between *real* validators, and + /// a reader checking the run against the cluster should find the ones they + /// would have chosen from too. + /// + /// The order is the cluster's, made deterministic by sorting on stake and + /// then on the key, so two runs against the same epoch pick the same + /// validators and a reader can reproduce the selection instead of taking the + /// list on trust. + pub fn active_vote_accounts(&self) -> Result> { + let v = self.call( + "getVoteAccounts", + serde_json::json!([{ "commitment": "confirmed" }]), + )?; + let current = v + .get("current") + .and_then(|c| c.as_array()) + .ok_or_else(|| anyhow!("getVoteAccounts: no current validators"))?; + let mut ranked: Vec<(u64, String)> = current + .iter() + .filter_map(|entry| { + let key = entry.get("votePubkey")?.as_str()?.to_string(); + let stake = entry.get("activatedStake")?.as_u64()?; + Some((stake, key)) + }) + .collect(); + ranked.sort_by(|a, b| b.0.cmp(&a.0).then_with(|| a.1.cmp(&b.1))); + ranked + .into_iter() + .map(|(_, key)| key.parse().map_err(|e| anyhow!("vote account {key}: {e}"))) + .collect() + } + + /// What a landed transaction actually cost in compute. + /// + /// Read back from the cluster rather than simulated. A simulation runs + /// against a different slot with different account states, and the number + /// this is used for — how close a full settlement comes to the budget — is + /// only interesting if it is the number the validator metered. + pub fn compute_units(&self, signature: &str) -> Result { + let v = self.call( + "getTransaction", + serde_json::json!([ + signature, + { "commitment": "confirmed", "maxSupportedTransactionVersion": 0 } + ]), + )?; + v.pointer("/meta/computeUnitsConsumed") + .and_then(|c| c.as_u64()) + .ok_or_else(|| anyhow!("getTransaction: {signature} reports no compute units")) + } } diff --git a/crates/mirror-cli/src/crowd.rs b/crates/mirror-cli/src/crowd.rs new file mode 100644 index 00000000..09dfa889 --- /dev/null +++ b/crates/mirror-cli/src/crowd.rs @@ -0,0 +1,1073 @@ +//! Different members, different validators, one timestamp. +//! +//! `PROOF.md` shows the pool delegating stake as a member's authority, once, in +//! a batch whose other members were making payments. That establishes the +//! capability and leaves the harder question open: a batch of *identical* +//! actions is the easy case for an anonymity set, because there is nothing to +//! tell the members apart in the first place. The question worth answering is +//! what happens when the actions **diverge in content** — when each member picks +//! a different validator, which is exactly the behavioural pattern that +//! fingerprints a staker across time. +//! +//! So this run settles N delegations to N *different* validators in one +//! transaction, and reads every stake account back off the cluster to check that +//! each member got the validator they asked for. +//! +//! It also measures what that costs, because it does cost something. A +//! transaction names each distinct account once, and a batch of divergent +//! actions names more distinct accounts than a batch of uniform ones — one extra +//! vote account per member. The 1232-byte packet is therefore reached sooner, +//! and **the anonymity set a divergent batch can hold is smaller than the one a +//! uniform batch can hold**. That is a real trade-off in the design and it is +//! better measured than left for a reader to discover. +//! +//! Nothing here is simulated. The ceiling is found by serializing real +//! instructions, and the batch at that ceiling is settled against a live +//! cluster. + +use crate::chain::Chain; +use crate::note::StoredNote; +use crate::soak::{keys, read_keypair, Settlement, Step}; +use crate::stake; +use anyhow::{anyhow, Context, Result}; +use mirror_circuit::{prove, Keys, Witness}; +use mirror_core::{MerkleTree, Note}; +use mirror_pool_program::{ + instruction::Instruction as MirrorIx, + pda::{pool_address, spend_address, vault_address}, + processor::SELECTOR_INVOKE_SIGNED, +}; +use solana_keypair::Keypair; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use solana_signer::Signer; +use solana_transaction::Transaction; + +/// A pool is unique per denomination, so this constant also selects the pool. +/// Bump it for a clean run; the note ledger is keyed by it, so a new +/// denomination starts a new ledger too. +const DENOMINATION: u64 = 43_000_007; // 0.043 SOL +const ENTRY_FEE: u64 = 0; +const RELAY_FEE: u64 = 200_000; + +/// What the operator puts into each stake account before the pool delegates it. +/// +/// Devnet's minimum delegation is 1 SOL, which no sane pool denomination +/// reaches: a pool whose notes each cleared the floor would need six-plus SOL to +/// fill a single batch of this size. The operator funds the account and the +/// member's escrow is added to it. What the *pool* supplies is the authority, +/// which is the part being demonstrated. +const STAKE_FUNDING: u64 = 1_100_000_000; + +/// Enough for a relay to pay for its own `submit_spend` and stay rent-exempt. +/// It is repaid at settlement out of the denomination. +const RELAY_FUNDING: u64 = 5_000_000; + +/// The 1280-byte IPv6 minimum MTU, less a 40-byte IPv6 header and an 8-byte +/// fragment header. Written out rather than imported because the crates this +/// binary already depends on do not re-export it, and a number a reader can +/// derive is worth more than one they have to trust. +const PACKET_DATA_SIZE: usize = 1280 - 40 - 8; + +/// What a transaction carrying a single non-budget instruction is given unless +/// it asks for more. Asking costs a second instruction, and a second instruction +/// costs bytes a full settlement has none of. +const DEFAULT_COMPUTE_BUDGET: u64 = 200_000; + +/// One member's delegation, and the validator they chose. +pub struct Member { + pub stake: Pubkey, + pub vote: Pubkey, + pub relay: Pubkey, +} + +/// What the run measured about how many members fit. +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Ceiling { + /// Members per settlement when each chooses a different validator. + pub divergent: usize, + /// Members per settlement when they all choose the same one. + pub uniform: usize, + /// Wire size of a full divergent settlement. + pub divergent_bytes: usize, + /// Wire size of one member more than fits. + pub over_bytes: usize, + /// What adding a `SetComputeUnitLimit` instruction costs in bytes. + /// + /// Measured because the two limits turn out to be coupled. Raising the + /// compute budget is the obvious answer to a settlement that runs out of it, + /// and it is paid for in the one currency a full batch has none of. + pub budget_bytes: usize, +} + +/// Everything the report is rendered from. +/// +/// Written after the run succeeds and read back by `--render-only`, so that +/// improving a sentence in the report costs nothing. A document generated from a +/// live run is worth more than a hand-written one, and it is only worth more +/// while regenerating it is cheap — otherwise the prose drifts away from the +/// generator that is supposed to produce it, quietly. +/// Addresses are stored as base58 text rather than as the byte arrays a +/// `Pubkey` serializes into, because this file is meant to be read: every value +/// in it can be pasted into an explorer and checked against the cluster, which +/// is the only reason to keep it at all. +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Outcome { + pub pool: String, + pub vault: String, + pub denomination: u64, + pub ceiling: Ceiling, + pub roster: Vec, + pub signature: String, + pub bytes: usize, + pub compute_units: u64, + pub steps: Vec, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct MemberRecord { + pub stake: String, + pub vote: String, + pub relay: String, +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub struct StepRecord { + pub name: String, + pub signature: String, + pub note: String, +} + +pub struct Crowd { + client: Chain, + program_id: Pubkey, + payer: Keypair, + pool: Pubkey, + vault: Pubkey, + pub steps: Vec, +} + +impl Crowd { + pub fn new(url: &str, program_id: Pubkey, payer: Keypair) -> Self { + let (pool, _) = pool_address(&program_id, DENOMINATION); + let (vault, _) = vault_address(&program_id, &pool); + Crowd { + client: Chain::new(url), + program_id, + payer, + pool, + vault, + steps: Vec::new(), + } + } + + pub fn pool(&self) -> Pubkey { + self.pool + } + pub fn vault(&self) -> Pubkey { + self.vault + } + + fn send(&self, ix: Instruction, signers: &[&Keypair]) -> Result { + let blockhash = self.client.latest_blockhash()?; + let message = solana_message::Message::new(&[ix], Some(&signers[0].pubkey())); + let tx = Transaction::new(signers, message, blockhash); + self.client.send(&tx) + } + + fn record(&mut self, name: &'static str, signature: String, note: String) { + println!(" {name:<28} {signature}"); + self.steps.push(Step { + name, + signature, + note, + }); + } + + fn pool_metas(&self) -> Vec { + vec![ + AccountMeta::new(self.payer.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(self.vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ] + } + + /// Creates the pool, unless a previous run already did. + /// + /// Notes left in the pool are allowed here, and that is deliberate. A run + /// against a live cluster gets interrupted — a public endpoint times out + /// mid-confirmation and the deposits are already made — and a tool that + /// refuses to continue turns a transient network error into escrow nobody + /// releases. What must not happen is a run *reporting* a batch it did not + /// assemble, and that is checked where it can be checked properly: the + /// leftovers must number exactly what this run needs, the ledger must hold + /// their secrets, and every settled delegation is read back off the cluster + /// at the end. + pub fn init_pool(&mut self, k_floor: u32, members: usize) -> Result<()> { + if let Some(mut data) = self.client.account_data(&self.pool)? { + let pool = mirror_pool_program::Pool::load(&mut data) + .map_err(|e| anyhow!("the account at the pool address is not a pool: {e:?}"))?; + let outstanding = pool + .outstanding_notes() + .map_err(|e| anyhow!("reading the pool: {e:?}"))?; + if outstanding as usize > members { + return Err(anyhow!( + "the pool for denomination {DENOMINATION} holds {outstanding} unspent \ + note(s) and this run needs {members}, so it cannot be the remains of \ + this run. Raise DENOMINATION in crowd.rs for a clean pool — the note \ + ledger is keyed by it, so a new denomination starts a new ledger too." + )); + } + if pool.k_floor() != k_floor { + return Err(anyhow!( + "the existing pool's floor is {} and this run needs {k_floor}. \ + A pool's floor is fixed at creation, so raise DENOMINATION for a \ + fresh one.", + pool.k_floor() + )); + } + if outstanding > 0 { + println!(" pool exists and holds {outstanding} note(s) from an interrupted run"); + } else { + println!(" pool already exists and is empty, reusing it"); + } + return Ok(()); + } + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::InitPool { + denomination: DENOMINATION, + entry_fee: ENTRY_FEE, + k_floor, + } + .pack(), + self.pool_metas(), + ); + let payer = self.payer.insecure_clone(); + let sig = self.send(ix, &[&payer])?; + self.record( + "init_pool", + sig, + format!("denomination {DENOMINATION}, k_floor {k_floor}"), + ); + Ok(()) + } + + fn leaf_count(&self) -> Result { + let mut data = self + .client + .account_data(&self.pool) + .context("reading the pool account")? + .ok_or_else(|| anyhow!("the pool account does not exist"))?; + let pool = mirror_pool_program::Pool::load(&mut data).map_err(|e| anyhow!("{e:?}"))?; + Ok(pool.deposit_count()) + } + + /// Deposits until the pool holds `target` notes, returning the host tree and + /// every note in it. + /// + /// Stated as a target rather than a count so that resuming is the same code + /// path as starting: a run interrupted after four of six deposits makes two + /// more, and one interrupted after all six makes none. + /// + /// The ledger is written after every single deposit rather than at the end, + /// because the program keeps only the accumulator's frontier: a note whose + /// secrets are lost after its commitment reached the tree is escrow nobody + /// can ever release. An interrupted run must leave a file that still matches + /// the chain. + pub fn deposit( + &mut self, + target: usize, + ledger: &std::path::Path, + ) -> Result<(MerkleTree, Vec)> { + let mut tree = MerkleTree::new().map_err(|e| anyhow!("{e}"))?; + let mut notes = Vec::new(); + + let mut stored: Vec = if ledger.exists() { + serde_json::from_str(&std::fs::read_to_string(ledger)?)? + } else { + Vec::new() + }; + let on_chain = self.leaf_count()? as usize; + if stored.len() != on_chain { + return Err(anyhow!( + "the note ledger holds {} notes but the pool has {on_chain} leaves. \ + They must agree, or the host tree cannot match the program's root. \ + Use a fresh denomination, or restore the ledger.", + stored.len() + )); + } + for note in &stored { + let note = note.note()?; + tree.insert(note.commitment().map_err(|e| anyhow!("{e}"))?) + .map_err(|e| anyhow!("{e}"))?; + notes.push(note); + } + + if stored.len() > target { + return Err(anyhow!( + "the pool already holds {} notes and this run wants {target}", + stored.len() + )); + } + if let Some(parent) = ledger.parent() { + std::fs::create_dir_all(parent)?; + } + for _ in stored.len()..target { + let note = crate::note::generate(DENOMINATION)?; + let commitment = note.commitment().map_err(|e| anyhow!("{e}"))?; + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::Deposit { + commitment: commitment.to_bytes(), + } + .pack(), + self.pool_metas(), + ); + let payer = self.payer.insecure_clone(); + let sig = self.send(ix, &[&payer])?; + self.record("deposit", sig, format!("note {}", notes.len() + 1)); + tree.insert(commitment).map_err(|e| anyhow!("{e}"))?; + notes.push(note); + stored.push(StoredNote::from_note(¬e, DENOMINATION)?); + std::fs::write(ledger, serde_json::to_string(&stored)?)?; + } + Ok((tree, notes)) + } + + /// The pending spend this note already has, if an interrupted run submitted + /// one. + /// + /// Rebuilt from the chain rather than from a file, because it can be: a + /// note's nullifier is a function of the note alone, the record's address is + /// a function of the nullifier, and the record itself carries the two + /// accounts settlement has to name. A state file would be a second source of + /// truth for facts the chain already holds, and the failure it invites — a + /// file that disagrees with the cluster — is worse than the one it prevents. + /// + /// A record that is already settled returns `None`: that note is finished, + /// and re-submitting it would burn a nullifier that is already burnt. + pub fn existing_spend(&self, note: &Note) -> Result> { + let nullifier = note.nullifier().map_err(|e| anyhow!("{e}"))?.to_bytes(); + let (spend_pda, _) = spend_address(&self.program_id, &self.pool, &nullifier); + let Some(mut data) = self.client.account_data(&spend_pda)? else { + return Ok(None); + }; + let record = mirror_pool_program::spend::Spend::load(&mut data) + .map_err(|e| anyhow!("the account at {spend_pda} is not a spend record: {e:?}"))?; + if record.status() != mirror_pool_program::spend::STATUS_PENDING { + return Ok(None); + } + let beneficiary = Pubkey::new_from_array(record.beneficiary()); + Ok(Some(Settlement { + spend: spend_pda, + beneficiary, + relay: Pubkey::new_from_array(record.relay()), + target: Some(Pubkey::new_from_array(record.target_program())), + // The vote account is not recoverable from the record, because the + // proof never bound it — `DelegateStake` carries the validator in an + // account slot rather than in its instruction data. The caller + // supplies it from the plan, and the read-back after settlement is + // what turns that into a checked claim. + action_accounts: Vec::new(), + })) + } + + /// Gives a relay enough to pay for the one transaction it signs. + /// + /// Every member gets their own relay here, which is the worst case for the + /// packet and the one a settler has to plan against: a batch whose members + /// shared a relay names fewer distinct keys and fits more of them. + pub fn fund_relay(&mut self, relay: &Pubkey) -> Result<()> { + let ix = solana_system_interface::instruction::transfer( + &self.payer.pubkey(), + relay, + RELAY_FUNDING, + ); + let payer = self.payer.insecure_clone(); + self.send(ix, &[&payer])?; + Ok(()) + } + + /// A stake account whose **staker** is the pool's vault and whose + /// **withdrawer** is not. + pub fn create_stake_account(&mut self) -> Result { + let stake_kp = Keypair::new(); + let [create, initialise] = stake::create( + &self.payer.pubkey(), + &stake_kp.pubkey(), + &self.vault, + &self.payer.pubkey(), + STAKE_FUNDING, + )?; + let blockhash = self.client.latest_blockhash()?; + let message = + solana_message::Message::new(&[create, initialise], Some(&self.payer.pubkey())); + let payer = self.payer.insecure_clone(); + let tx = Transaction::new(&[&payer, &stake_kp], message, blockhash); + let sig = self.client.send(&tx)?; + self.record( + "create stake account", + sig, + format!("{STAKE_FUNDING} lamports, staker = the pool's vault"), + ); + Ok(stake_kp.pubkey()) + } + + /// Submits one member's spend: delegate `stake` to `vote`, authorised by the + /// pool, signed by a relay that is not the member. + #[allow(clippy::too_many_arguments)] + pub fn submit_delegation( + &mut self, + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + stake_account: &Pubkey, + vote: &Pubkey, + relay: &Keypair, + ) -> Result { + use ark_std::rand::SeedableRng; + let stake_program = stake::program_id()?; + let merkle_proof = tree.proof(index as u64).map_err(|e| anyhow!("{e}"))?; + // The stake account is the beneficiary, so the member's escrow lands in + // the stake they authorised rather than beside it. + // + // Note what the binding does *not* cover: the vote account. The proof + // fixes the selector, the target program, the beneficiary, the fee, the + // account *count* and the payload — and `DelegateStake` carries the + // validator in an account slot, not in its four bytes of instruction + // data. Which validator a member gets is therefore chosen at settlement. + // That is the account-slot limit `THREAT_MODEL.md` states, met head-on: + // the check below is a read-back of every stake account, because the + // proof cannot make this promise and something has to. + let binding = mirror_core::action_binding( + SELECTOR_INVOKE_SIGNED, + &stake_program.to_bytes(), + &stake_account.to_bytes(), + RELAY_FEE, + stake::DELEGATE_ACCOUNTS, + &stake::DELEGATE_STAKE, + ); + let witness = Witness { + note: notes[index], + merkle_proof: &merkle_proof, + root: tree.root().map_err(|e| anyhow!("{e}"))?, + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([index as u8 + 1; 32]); + let proof = prove(keys, &witness, &mut rng).map_err(|e| anyhow!("{e}"))?; + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&self.program_id, &self.pool, &nullifier); + + let ix = Instruction::new_with_bytes( + self.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR_INVOKE_SIGNED, + target_program: stake_program.to_bytes(), + beneficiary: stake_account.to_bytes(), + relay_fee: RELAY_FEE, + action_accounts: stake::DELEGATE_ACCOUNTS, + payload: stake::DELEGATE_STAKE.to_vec(), + } + .pack(), + vec![ + AccountMeta::new(relay.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let sig = self.send(ix, &[relay])?; + self.record( + "submit_spend", + sig, + format!("note {index}, relay-signed, delegate to {vote}"), + ); + Ok(spend_pda) + } + + fn settle_ix(&self, batch: &[Settlement]) -> Instruction { + let mut metas = vec![ + AccountMeta::new(self.payer.pubkey(), true), + AccountMeta::new(self.pool, false), + AccountMeta::new(self.vault, false), + ]; + for entry in batch { + metas.push(AccountMeta::new(entry.spend, false)); + metas.push(AccountMeta::new(entry.beneficiary, false)); + metas.push(AccountMeta::new(entry.relay, false)); + if let Some(target) = entry.target { + metas.push(AccountMeta::new_readonly(target, false)); + // The vault is among these, and it reaches the callee as a + // *signer*. The program sets that flag itself from seeds no + // settler holds, which is why the meta here says otherwise. + metas.extend(entry.action_accounts.iter().cloned()); + } + } + Instruction::new_with_bytes( + self.program_id, + &MirrorIx::SettleEpoch { + count: batch.len() as u8, + } + .pack(), + metas, + ) + } + + /// What this settlement would weigh on the wire. + /// + /// A legacy transaction is a compact array of 64-byte signatures followed by + /// the serialized message, and `Message::serialize` is what a validator + /// receives rather than an approximation of it. Settlement is signed by the + /// settler alone, so the signature array is one entry and its length prefix + /// one byte. + pub fn settlement_bytes(&self, batch: &[Settlement]) -> usize { + let ix = self.settle_ix(batch); + let message = solana_message::Message::new(&[ix], Some(&self.payer.pubkey())); + 1 + 64 + message.serialize().len() + } + + /// What the same settlement would weigh with a `SetComputeUnitLimit` + /// instruction in front of it. + /// + /// The difference is the price of asking for more compute. It is worth a + /// number rather than an argument, because for a batch of cross-program + /// actions the compute budget and the packet stop being independent limits: + /// the escape from one is paid for out of the other. + pub fn settlement_bytes_with_budget(&self, batch: &[Settlement]) -> usize { + // `ComputeBudgetInstruction::SetComputeUnitLimit`: a u8 discriminant of + // 2 followed by a little-endian u32. The program takes no accounts, so + // what this costs is its own key plus the instruction's framing. + let budget: Pubkey = "ComputeBudget111111111111111111111111111111" + .parse() + .expect("the compute budget program id is a const"); + let mut data = vec![2u8]; + data.extend_from_slice(&u32::MAX.to_le_bytes()); + let raise = Instruction::new_with_bytes(budget, &data, vec![]); + let message = solana_message::Message::new( + &[raise, self.settle_ix(batch)], + Some(&self.payer.pubkey()), + ); + 1 + 64 + message.serialize().len() + } + + pub fn settle(&mut self, batch: &[Settlement]) -> Result<(String, usize)> { + let bytes = self.settlement_bytes(batch); + if bytes > PACKET_DATA_SIZE { + return Err(anyhow!( + "this settlement is {bytes} bytes, {} over the {PACKET_DATA_SIZE}-byte \ + packet limit, and no validator would accept it", + bytes - PACKET_DATA_SIZE + )); + } + let ix = self.settle_ix(batch); + let payer = self.payer.insecure_clone(); + let sig = self.send(ix, &[&payer])?; + self.record( + "settle_epoch", + sig.clone(), + format!( + "{} delegations to {} different validators, one transaction, {bytes} bytes", + batch.len(), + batch.len() + ), + ); + Ok((sig, bytes)) + } + + pub fn compute_units(&self, signature: &str) -> Result { + self.client.compute_units(signature) + } + + /// Reads a stake account back off the cluster and returns the validator it + /// actually backs. + pub fn delegated_voter(&self, stake_account: &Pubkey) -> Result { + let data = self + .client + .account_data(stake_account)? + .ok_or_else(|| anyhow!("the stake account {stake_account} vanished"))?; + stake::delegated_voter(&data) + } + + /// How many members fit in one settlement, found by serializing rather than + /// by arithmetic. + /// + /// Placeholder keys are legitimate here because transaction size depends on + /// how many *distinct* accounts a message names, not on which — and the real + /// batch is measured again before it is sent, so an error in that reasoning + /// surfaces as a refusal rather than as a wrong number in a document. + /// + /// Both shapes are measured because the difference between them is the + /// finding. `divergent` gives every member their own vote account; + /// `uniform` shares one across the batch, which is the same run of members + /// all choosing the same validator. + pub fn measure_ceiling(&self) -> Ceiling { + let shape = |n: usize, share_vote: bool| -> Vec { + let shared_vote = Pubkey::new_unique(); + (0..n) + .map(|_| { + let stake_account = Pubkey::new_unique(); + let vote = if share_vote { + shared_vote + } else { + Pubkey::new_unique() + }; + Settlement { + spend: Pubkey::new_unique(), + beneficiary: stake_account, + relay: Pubkey::new_unique(), + target: Some(stake::program_id().expect("the stake program id is a const")), + action_accounts: stake::delegate_accounts( + &stake_account, + &vote, + &self.vault, + ) + .expect("the delegate account list is built from consts"), + } + }) + .collect() + }; + let largest = |share_vote: bool| -> usize { + // Searched from the top down rather than counted upward, so a + // hypothetical non-monotonic encoding would be caught by the + // round-trip check against the batch that actually lands rather + // than hidden by an early exit. + (1..=32) + .rfind(|n| self.settlement_bytes(&shape(*n, share_vote)) <= PACKET_DATA_SIZE) + .unwrap_or(0) + }; + let divergent = largest(false); + let uniform = largest(true); + let full = shape(divergent, false); + let divergent_bytes = self.settlement_bytes(&full); + Ceiling { + divergent, + uniform, + divergent_bytes, + over_bytes: self.settlement_bytes(&shape(divergent + 1, false)), + budget_bytes: self.settlement_bytes_with_budget(&full) - divergent_bytes, + } + } +} + +pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Result<()> { + let program_id: Pubkey = program + .parse() + .map_err(|e| anyhow!("bad program id: {e}"))?; + let payer = read_keypair(keypair)?; + println!("program {program_id}"); + println!("payer {}", payer.pubkey()); + println!("cluster {url}\n"); + + let mut crowd = Crowd::new(url, program_id, payer.insecure_clone()); + + // The ceiling first, before anything is spent. How many stake accounts to + // create is the answer to this question, and creating the wrong number means + // either a batch that no validator will accept or SOL left in accounts the + // run has no use for. + let ceiling = crowd.measure_ceiling(); + println!("how many members fit in one settlement, by serializing the real instruction:"); + println!( + " all delegating to the same validator {} members", + ceiling.uniform + ); + println!( + " each delegating to a different one {} members ({} bytes, {} to spare)", + ceiling.divergent, + ceiling.divergent_bytes, + PACKET_DATA_SIZE - ceiling.divergent_bytes + ); + println!( + " one more than that {} bytes, {} over the {PACKET_DATA_SIZE}-byte limit\n", + ceiling.over_bytes, + ceiling.over_bytes - PACKET_DATA_SIZE + ); + let members = ceiling.divergent; + if members < 2 { + return Err(anyhow!( + "only {members} member(s) fit, so there is no crowd to measure" + )); + } + + // Validators the cluster itself counts as active, so a reader checking this + // run finds the delegations pointing at validators they could have chosen. + // + // Fixed to a file on the first run and reused after, because the ranking + // moves between epochs and a resumed run that re-drew the list would settle + // members against validators other than the ones they asked for — silently, + // since the proof binds the account *count* and never the accounts. + let plan_path = format!("data/crowd-plan-{DENOMINATION}.json"); + let votes = crowd_votes(url, members, std::path::Path::new(&plan_path))?; + println!("validators drawn from the cluster's active set:"); + for (i, v) in votes.iter().enumerate() { + println!(" member {i} {v}"); + } + + let cost = members as u64 * (STAKE_FUNDING + DENOMINATION + RELAY_FUNDING); + println!( + "\nthis run will spend about {:.2} SOL: {members} stake accounts, {members} deposits, \ + {members} relays\n", + cost as f64 / 1e9 + ); + + println!("deriving the proving key from the published seed (this takes a moment)"); + let keys = keys()?; + + println!("\nsetting up:"); + crowd.init_pool(members as u32, members)?; + let ledger_path = format!("data/crowd-notes-{DENOMINATION}.json"); + let ledger = std::path::Path::new(&ledger_path); + let (tree, notes) = crowd.deposit(members, ledger)?; + + let mut batch = Vec::new(); + let mut roster = Vec::new(); + for (i, vote) in votes.iter().enumerate() { + // A spend this note already carries is reused rather than remade. The + // nullifier is spend-once-ever, so a second submission for the same note + // is refused by the program — which would strand the run at exactly the + // point a flaky endpoint left it. + let entry = match crowd.existing_spend(¬es[i])? { + Some(found) => { + println!(" member {i:<21} spend {} already submitted", found.spend); + Settlement { + action_accounts: stake::delegate_accounts( + &found.beneficiary, + vote, + &crowd.vault(), + )?, + ..found + } + } + None => { + let stake_account = crowd.create_stake_account()?; + let relay = Keypair::new(); + crowd.fund_relay(&relay.pubkey())?; + let spend = crowd.submit_delegation( + &keys, + &tree, + ¬es, + i, + &stake_account, + vote, + &relay, + )?; + Settlement { + spend, + beneficiary: stake_account, + relay: relay.pubkey(), + target: Some(stake::program_id()?), + action_accounts: stake::delegate_accounts( + &stake_account, + vote, + &crowd.vault(), + )?, + } + } + }; + roster.push(Member { + stake: entry.beneficiary, + vote: *vote, + relay: entry.relay, + }); + batch.push(entry); + } + + println!("\nsettling all {members} in one transaction:"); + let (signature, bytes) = crowd.settle(&batch)?; + let cu = crowd.compute_units(&signature)?; + println!(" {bytes} bytes of {PACKET_DATA_SIZE}, {cu} CU of {DEFAULT_COMPUTE_BUDGET}"); + println!( + " raising the compute budget would cost {} bytes and there are {} to spare", + ceiling.budget_bytes, + PACKET_DATA_SIZE - bytes + ); + if bytes != ceiling.divergent_bytes { + return Err(anyhow!( + "the settlement that landed was {bytes} bytes and the measurement predicted \ + {}, so the ceiling above describes a different transaction than the one sent", + ceiling.divergent_bytes + )); + } + + // The read-back. The proof binds how many accounts the call takes and never + // which, so nothing before this point can promise a member got the validator + // they asked for — the account state is the only place that answer exists. + println!("\nreading every stake account back off the cluster:"); + for (i, member) in roster.iter().enumerate() { + let voter = crowd.delegated_voter(&member.stake)?; + if voter != member.vote { + return Err(anyhow!( + "member {i} asked to delegate to {} and the stake account backs {voter}", + member.vote + )); + } + println!(" member {i} {} → {voter}", member.stake); + } + + let distinct: std::collections::HashSet<_> = roster.iter().map(|m| m.vote).collect(); + if distinct.len() != members { + return Err(anyhow!( + "the batch names {} distinct validators for {members} members, so it does not \ + demonstrate divergence", + distinct.len() + )); + } + println!( + "\n{members} members, {} distinct validators, one transaction, one timestamp.", + distinct.len() + ); + + let outcome = Outcome { + pool: crowd.pool().to_string(), + vault: crowd.vault().to_string(), + denomination: DENOMINATION, + ceiling, + roster: roster + .iter() + .map(|m| MemberRecord { + stake: m.stake.to_string(), + vote: m.vote.to_string(), + relay: m.relay.to_string(), + }) + .collect(), + signature, + bytes, + compute_units: cu, + steps: crowd + .steps + .iter() + .map(|s| StepRecord { + name: s.name.to_string(), + signature: s.signature.clone(), + note: s.note.clone(), + }) + .collect(), + }; + let result_path = format!("data/crowd-result-{DENOMINATION}.json"); + std::fs::write(&result_path, serde_json::to_string_pretty(&outcome)?)?; + write_report(&outcome, out)?; + println!("wrote {} and {result_path}", out.display()); + Ok(()) +} + +fn write_report(outcome: &Outcome, out: &std::path::Path) -> Result<()> { + if let Some(parent) = out.parent() { + std::fs::create_dir_all(parent)?; + } + std::fs::write(out, report(outcome))?; + Ok(()) +} + +/// Re-renders the report from the last run's recorded results. +/// +/// Touches no cluster and spends nothing. The numbers are exactly the ones the +/// run measured; only the sentences around them can change. +pub fn render_only(out: &std::path::Path) -> Result<()> { + let result_path = format!("data/crowd-result-{DENOMINATION}.json"); + let text = std::fs::read_to_string(&result_path).with_context(|| { + format!("{result_path} does not exist — there is no recorded run to render") + })?; + let outcome: Outcome = serde_json::from_str(&text)?; + write_report(&outcome, out)?; + println!("rendered {} from {result_path}", out.display()); + Ok(()) +} + +/// Picks `n` distinct active validators, largest stake first, and remembers the +/// choice. +/// +/// The file is the record of what each member *asked for*. It has to exist +/// somewhere outside the chain, because the chain does not hold it: a member's +/// proof binds the number of accounts their call takes and not which accounts +/// fill the slots, so before settlement there is nothing on-chain that says +/// which validator member 3 wanted. The read-back afterwards compares the +/// cluster against this file, which is what makes it a check rather than a +/// restatement. +fn crowd_votes(url: &str, n: usize, plan: &std::path::Path) -> Result> { + if plan.exists() { + let saved: Vec = serde_json::from_str(&std::fs::read_to_string(plan)?)?; + if saved.len() != n { + return Err(anyhow!( + "{} names {} validators and this run needs {n}. It belongs to a run of a \ + different size; delete it only if no spend has been submitted against it.", + plan.display(), + saved.len() + )); + } + println!("(reusing the validator choices in {})", plan.display()); + return saved + .iter() + .map(|s| s.parse().map_err(|e| anyhow!("{s}: {e}"))) + .collect(); + } + let all = Chain::new(url).active_vote_accounts()?; + if all.len() < n { + return Err(anyhow!( + "the cluster reports {} active validators and this run needs {n}", + all.len() + )); + } + let chosen: Vec = all.into_iter().take(n).collect(); + if let Some(parent) = plan.parent() { + std::fs::create_dir_all(parent)?; + } + let names: Vec = chosen.iter().map(|v| v.to_string()).collect(); + std::fs::write(plan, serde_json::to_string_pretty(&names)?)?; + Ok(chosen) +} + +fn explorer(kind: &str, id: &str) -> String { + format!("[`{id}`](https://explorer.solana.com/{kind}/{id}?cluster=devnet)") +} + +fn report(outcome: &Outcome) -> String { + let Outcome { + ceiling, + roster, + bytes, + steps, + .. + } = outcome; + let (signature, cu) = (&outcome.signature, outcome.compute_units); + let denomination = outcome.denomination; + let bytes = *bytes; + let members = roster.len(); + let mut md = String::new(); + md.push_str("# Different members, different validators, one timestamp\n\n"); + md.push_str( + "A batch whose members all do the same thing is the easy case for an anonymity set: \ + there is nothing to tell them apart to begin with. This run does the hard one. Each \ + member delegates stake to a **different validator**, and all of it settles in a \ + single transaction.\n\n\ + Validator choice is the behavioural pattern worth hiding. It is stable, it is \ + public, and it fingerprints a staker across epochs far more reliably than an \ + amount does. What an observer gets from the transaction below is a set of \ + delegations landing at one timestamp with no way to say which member asked for \ + which.\n\n", + ); + md.push_str(&format!( + "Generated by `mirror crowd`. Pool {}, vault {}, denomination {denomination} lamports.\n\n", + explorer("address", &outcome.pool), + explorer("address", &outcome.vault) + )); + + md.push_str("## The settlement\n\n"); + md.push_str(&format!("{}\n\n", explorer("tx", signature))); + md.push_str(&format!( + "| | |\n|---|---|\n| members | {members} |\n| distinct validators | {members} |\n\ + | wire size | {bytes} of {PACKET_DATA_SIZE} bytes |\n\ + | compute | {cu} of {DEFAULT_COMPUTE_BUDGET} CU |\n\n" + )); + + md.push_str("## Who got what\n\n"); + md.push_str( + "Read back from the cluster after settlement, not assumed from what was requested. \ + A member's proof binds how many accounts their call takes and never *which*, so \ + the stake account's own state is the only place the answer exists — this table is \ + the check that limit needs, and the run fails if any row disagrees.\n\n\ + Every member has their own relay, and no relay key appears twice. That is the \ + worst case for the packet and the case a settler has to plan against: a batch \ + whose members shared a relay would name fewer distinct keys and fit more of \ + them.\n\n", + ); + md.push_str("| member | stake account | delegated to | relay |\n|---|---|---|---|\n"); + for (i, m) in roster.iter().enumerate() { + md.push_str(&format!( + "| {i} | {} | {} | {} |\n", + explorer("address", &m.stake), + explorer("address", &m.vote), + explorer("address", &m.relay) + )); + } + md.push('\n'); + + md.push_str("## What divergence costs\n\n"); + md.push_str(&format!( + "Divergence is not free, and the price is anonymity-set size. A transaction names \ + each distinct account once, so a member who picks their own validator adds a vote \ + account nobody else in the batch names. The 1232-byte packet is reached sooner.\n\n\ + | batch | members per settlement |\n|---|---|\n\ + | all delegating to the same validator | {} |\n\ + | each delegating to a different one | {} |\n\n\ + At {} members the settlement weighs {} bytes with {} to spare; one more member \ + weighs {} bytes, {} over the limit. Both figures come from serializing the real \ + instruction, and the settlement that landed above is {bytes} bytes — the same \ + number, which is what makes the measurement a prediction rather than a \ + description.\n\n", + ceiling.uniform, + ceiling.divergent, + ceiling.divergent, + ceiling.divergent_bytes, + PACKET_DATA_SIZE - ceiling.divergent_bytes, + ceiling.over_bytes, + ceiling.over_bytes - PACKET_DATA_SIZE, + )); + md.push_str("## And compute, which is closer than it is for payments\n\n"); + md.push_str(&format!( + "This settlement burned **{cu} CU of the {DEFAULT_COMPUTE_BUDGET}** a single \ + instruction gets by default — {:.0}% of the budget, for {members} payouts and \ + {members} cross-program invocations. That is a different regime from a batch of \ + plain transfers: `ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_\ + the_eleventh` settles ten of those in 19,545 CU, where compute is nowhere in the \ + conversation. A delegation costs roughly an order of magnitude more per member \ + than a payment does.\n\n\ + Each member's Groth16 proof was verified earlier, in their own `submit_spend`, \ + which is measured at 101,127 CU. That is why the two phases exist: verifying \ + {members} proofs here would cost over 600,000 CU — comfortably past the 200,000 a \ + single instruction gets by default, and a large fraction of the 1.4M a whole \ + transaction may ever request.\n\n", + cu as f64 * 100.0 / DEFAULT_COMPUTE_BUDGET as f64 + )); + md.push_str(&format!( + "The packet still binds first — {} members is where the bytes run out, and the \ + budget is not exhausted there — but the two limits are no longer independent, and \ + that is the part worth stating plainly. The usual answer to a settlement that runs \ + out of compute is to ask for more with a `SetComputeUnitLimit` instruction. \ + Measured against this very batch, that instruction costs **{} bytes**, and a full \ + settlement has {} to spare. **Raising the budget means dropping a member.**\n\n\ + So the ceiling here is not a byte count that a future compute optimisation would \ + lift. It is the point where the only two exits are closed at once, and the answer \ + to a larger crowd is more transactions — which means more timestamps, which is a \ + real cost to the anonymity and the reason this number is worth knowing.\n\n", + ceiling.divergent, + ceiling.budget_bytes, + PACKET_DATA_SIZE - ceiling.divergent_bytes, + )); + + md.push_str("## Every step\n\n"); + md.push_str("| step | signature | note |\n|---|---|---|\n"); + for step in steps { + md.push_str(&format!( + "| {} | {} | {} |\n", + step.name, + explorer("tx", &step.signature), + step.note + )); + } + md.push('\n'); + + md.push_str("## Scope\n\n"); + md.push_str( + "Devnet, and one operator. This is a functional and quantitative result, not an \ + anonymity claim about a live crowd: the relays here were funded from the same \ + wallet that made the deposits, which is exactly the linkage `USAGE.md` tells a \ + real member to avoid. What the run establishes is that a batch of *divergent* \ + actions settles as one, that every member reached the validator they chose, and \ + what such a batch costs in packet space.\n\n\ + The withdraw authority on every stake account above is the operator, never the \ + pool. The pool's signature is available to every member, so an authority the vault \ + holds is an authority every member holds: delegation is safe on those terms — the \ + worst a member can do is re-delegate somebody else's stake — and withdrawal is \ + not. That is `THREAT_MODEL.md` applied rather than repeated.\n", + ); + md +} diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 1dbb903b..01bf46a2 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -7,9 +7,11 @@ use std::path::PathBuf; mod chain; mod client; +mod crowd; mod history; mod note; mod soak; +mod stake; /// The public devnet cluster, so the common case needs no flag. const DEFAULT_URL: &str = "https://api.devnet.solana.com"; @@ -193,6 +195,29 @@ enum Command { #[arg(long, default_value = "docs/PROOF.md")] out: PathBuf, }, + /// Settles a batch of stake delegations to *different* validators, and + /// measures what that divergence costs in packet space. + /// + /// The claim `Soak` makes is that the pool can be a member's authority. This + /// one asks the harder question: whether a crowd survives its members + /// wanting different things. + Crowd { + #[arg(long)] + program: String, + #[arg(long, default_value = "https://api.devnet.solana.com")] + url: String, + /// Payer and settler keypair. + #[arg(long, default_value = "~/.config/solana/id.json")] + keypair: String, + /// Where to write the evidence. + #[arg(long, default_value = "docs/CROWD.md")] + out: PathBuf, + /// Re-render the report from the last run's recorded results, without + /// touching the cluster. For fixing the prose around numbers that were + /// already measured. + #[arg(long)] + render_only: bool, + }, /// Recomputes the verifying key from a seed and reports its digest. /// /// This is the check a third party runs. It binds the *whole* key — alpha, @@ -958,6 +983,19 @@ fn main() -> Result<()> { keypair, out, } => soak::run(&program, &url, &keypair, &out), + Command::Crowd { + program, + url, + keypair, + out, + render_only, + } => { + if render_only { + crowd::render_only(&out) + } else { + crowd::run(&program, &url, &keypair, &out) + } + } Command::InitPool { program, diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs index ca6de3c0..cbd9e3bd 100644 --- a/crates/mirror-cli/src/soak.rs +++ b/crates/mirror-cli/src/soak.rs @@ -47,24 +47,15 @@ const MEMO_PROGRAM: &str = "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"; /// What the member asks the pool to say on their behalf. const MEMO_PAYLOAD: &[u8] = b"mirror-pool: signed by the pool, for a member"; -/// The native Stake program. -const STAKE_PROGRAM: &str = "Stake11111111111111111111111111111111111111"; -/// Still in `DelegateStake`'s account list, deprecated but not removed. -const STAKE_CONFIG: &str = "StakeConfig11111111111111111111111111111111"; -const SYSVAR_CLOCK: &str = "SysvarC1ock11111111111111111111111111111111"; -const SYSVAR_STAKE_HISTORY: &str = "SysvarStakeHistory1111111111111111111111111"; -const SYSVAR_RENT: &str = "SysvarRent111111111111111111111111111111111"; - -/// `StakeInstruction::DelegateStake`, a bare u32 discriminant. -const DELEGATE_STAKE: [u8; 4] = [2, 0, 0, 0]; - /// A devnet validator with a large active stake, so the delegation has somewhere /// real to go. +/// +/// One validator is enough for the claim this run makes, which is that the pool +/// can be a member's *authority*. Whether a batch survives its members choosing +/// **different** validators is a separate claim with a separate cost, and +/// `crowd.rs` measures it against the same cluster. const VOTE_ACCOUNT: &str = "2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv"; -/// `StakeStateV2` is 200 bytes whatever variant it holds. -const STAKE_ACCOUNT_LEN: usize = 200; - /// What the operator puts into the stake account before the pool delegates it. /// /// Devnet's minimum delegation is 1 SOL and this pool's denomination is 0.02, so @@ -475,35 +466,13 @@ impl Soak { /// operator. pub fn create_stake_account(&mut self) -> Result { let stake = Keypair::new(); - let stake_program: Pubkey = STAKE_PROGRAM - .parse() - .map_err(|e| anyhow!("stake id: {e}"))?; - let rent_sysvar: Pubkey = SYSVAR_RENT.parse().map_err(|e| anyhow!("rent id: {e}"))?; - - let create = solana_system_interface::instruction::create_account( + let [create, initialise] = crate::stake::create( &self.payer.pubkey(), &stake.pubkey(), + &self.vault, + &self.payer.pubkey(), STAKE_FUNDING, - STAKE_ACCOUNT_LEN as u64, - &stake_program, - ); - - // StakeInstruction::Initialize { Authorized, Lockup }, bincode: a u32 - // discriminant then the two authorities and a zero lockup. - let mut data = 0u32.to_le_bytes().to_vec(); - data.extend_from_slice(self.vault.as_ref()); // staker - data.extend_from_slice(&self.payer.pubkey().to_bytes()); // withdrawer - data.extend_from_slice(&0i64.to_le_bytes()); // lockup.unix_timestamp - data.extend_from_slice(&0u64.to_le_bytes()); // lockup.epoch - data.extend_from_slice(&[0u8; 32]); // lockup.custodian - let initialise = Instruction::new_with_bytes( - stake_program, - &data, - vec![ - AccountMeta::new(stake.pubkey(), false), - AccountMeta::new_readonly(rent_sysvar, false), - ], - ); + )?; let blockhash = self.client.latest_blockhash()?; let message = @@ -522,23 +491,11 @@ impl Soak { Ok(stake.pubkey()) } - /// The action list `DelegateStake` expects, in the callee's own order. - /// - /// Taken from what the `solana` CLI builds rather than from memory: the - /// authority sits in the last slot, and the deprecated config account is - /// still in the list. Getting this order wrong is not a compile error and - /// not a clear runtime one either — the stake program would read the config - /// account as the authority and report a missing signature. fn delegate_accounts(&self, stake: &Pubkey) -> Result> { - let parse = |s: &str| -> Result { s.parse().map_err(|e| anyhow!("{s}: {e}")) }; - Ok(vec![ - AccountMeta::new(*stake, false), - AccountMeta::new_readonly(parse(VOTE_ACCOUNT)?, false), - AccountMeta::new_readonly(parse(SYSVAR_CLOCK)?, false), - AccountMeta::new_readonly(parse(SYSVAR_STAKE_HISTORY)?, false), - AccountMeta::new_readonly(parse(STAKE_CONFIG)?, false), - AccountMeta::new_readonly(self.vault, false), - ]) + let vote: Pubkey = VOTE_ACCOUNT + .parse() + .map_err(|e| anyhow!("{VOTE_ACCOUNT}: {e}"))?; + crate::stake::delegate_accounts(stake, &vote, &self.vault) } /// Submits a spend whose action is a real stake delegation, authorised by @@ -554,9 +511,7 @@ impl Soak { relay: &Keypair, ) -> Result<(Pubkey, [u8; 32])> { use ark_std::rand::SeedableRng; - let stake_program: Pubkey = STAKE_PROGRAM - .parse() - .map_err(|e| anyhow!("stake id: {e}"))?; + let stake_program = crate::stake::program_id()?; let merkle_proof = tree.proof(index as u64).map_err(|e| anyhow!("{e}"))?; // The stake account is the beneficiary, so the member's escrow lands in // the stake they authorised rather than beside it. @@ -565,8 +520,8 @@ impl Soak { &stake_program.to_bytes(), &stake.to_bytes(), RELAY_FEE, - 6, - &DELEGATE_STAKE, + crate::stake::DELEGATE_ACCOUNTS, + &crate::stake::DELEGATE_STAKE, ); let witness = Witness { note: notes[index], @@ -591,8 +546,8 @@ impl Soak { target_program: stake_program.to_bytes(), beneficiary: stake.to_bytes(), relay_fee: RELAY_FEE, - action_accounts: 6, - payload: DELEGATE_STAKE.to_vec(), + action_accounts: crate::stake::DELEGATE_ACCOUNTS, + payload: crate::stake::DELEGATE_STAKE.to_vec(), } .pack(), vec![ @@ -611,37 +566,18 @@ impl Soak { Ok((spend_pda, nullifier)) } - /// Reads the stake account back and reports which validator it now backs. + /// Reads the stake account back off the cluster and reports which validator + /// it now backs. /// - /// `StakeStateV2` is a bincode enum: a u32 discriminant, then `Meta` (8 bytes - /// of rent reserve, two 32-byte authorities, a 48-byte lockup), then - /// `Delegation`, whose first field is the vote account. Variant 2 is `Stake`, - /// which an account only reaches by being delegated — an initialised but - /// undelegated account is variant 1, so the discriminant alone distinguishes - /// "the instruction landed" from "the delegation took". + /// The read is the evidence, not the signature. A landed transaction says + /// the instruction executed; only the account state says the delegation took + /// — `crate::stake::delegated_voter` is where that distinction is drawn. pub fn confirm_delegated(&mut self, stake: &Pubkey) -> Result<()> { let data = self .client .account_data(stake)? .ok_or_else(|| anyhow!("the stake account {stake} vanished"))?; - if data.len() < 156 { - return Err(anyhow!( - "the stake account is {} bytes, too short to be a stake state", - data.len() - )); - } - let discriminant = u32::from_le_bytes([data[0], data[1], data[2], data[3]]); - if discriminant != 2 { - return Err(anyhow!( - "the stake account is in state {discriminant}, not Stake(2): the \ - delegation did not take" - )); - } - let voter = Pubkey::new_from_array( - data[124..156] - .try_into() - .map_err(|_| anyhow!("reading the vote account"))?, - ); + let voter = crate::stake::delegated_voter(&data)?; println!(" stake account delegated to {voter}"); self.delegation = Some((*stake, voter)); Ok(()) @@ -875,9 +811,7 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re // timestamp, one signer and a batch of actions, with nothing separating the // member who staked from the members who merely moved value. let memo: Pubkey = MEMO_PROGRAM.parse().map_err(|e| anyhow!("memo id: {e}"))?; - let stake_program: Pubkey = STAKE_PROGRAM - .parse() - .map_err(|e| anyhow!("stake id: {e}"))?; + let stake_program = crate::stake::program_id()?; // The stake account exists before any member asks for anything, and who // created it is public. What the pool supplies is the authority to delegate @@ -1119,12 +1053,24 @@ fn write_proof(soak: &Soak, url: &str, out: &std::path::Path) -> Result<()> { if let Some(line) = &soak.signed_action { md.push_str("\n## The pool signed an action, and the callee said so\n\n"); - md.push_str( - "The settlement above carried four spends, and one of them was not a transfer: \ - the pool invoked SPL Memo as that member's **authority**, in the same \ - transaction as the other three. That is the capability a stake delegation or a \ + // Derived, not asserted. The batch size and the number of signed actions + // are both run parameters, and a sentence that hardcodes them goes stale + // the first time either changes -- which is exactly what happened when + // the stake delegation joined the batch. + let settled = soak + .accounting + .as_ref() + .map(|a| a.notes_settled) + .unwrap_or(0); + let signed = u64::from(soak.signed_action.is_some()) + u64::from(soak.delegation.is_some()); + md.push_str(&format!( + "The settlement above carried {settled} spends, and {signed} of them {} not a \ + transfer: the pool invoked SPL Memo as one member's **authority**, in the same \ + transaction as the other {}. That is the capability a stake delegation or a \ governance vote needs and a payment does not.\n\n", - ); + if signed == 1 { "was" } else { "were" }, + settled.saturating_sub(1), + )); md.push_str( "A signature only proves the transaction landed. It says nothing about who \ signed the instruction the pool made *inside* it, so the evidence has to come \ diff --git a/crates/mirror-cli/src/stake.rs b/crates/mirror-cli/src/stake.rs new file mode 100644 index 00000000..a4f3f7f2 --- /dev/null +++ b/crates/mirror-cli/src/stake.rs @@ -0,0 +1,221 @@ +//! The native Stake program, as much of it as a pool needs. +//! +//! Delegation is the case the pool's signature exists for. A member cannot be a +//! stake account's staker authority without appearing on chain and undoing the +//! point, so the pool is that authority and the pool signs — which is a +//! capability a payment never needs. +//! +//! Everything here is a byte layout belonging to somebody else's program, and +//! getting one wrong is not a compile error and rarely a clear runtime one: an +//! account list in the wrong order makes the stake program read the config +//! account as the authority and report a missing signature, which sends the +//! reader looking in the wrong place entirely. So the layouts live in one place +//! with the derivation written out, rather than in each caller that needs them. + +use anyhow::{anyhow, Result}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; + +pub const PROGRAM: &str = "Stake11111111111111111111111111111111111111"; +/// Still in `DelegateStake`'s account list, deprecated but not removed. +pub const CONFIG: &str = "StakeConfig11111111111111111111111111111111"; +pub const SYSVAR_CLOCK: &str = "SysvarC1ock11111111111111111111111111111111"; +pub const SYSVAR_STAKE_HISTORY: &str = "SysvarStakeHistory1111111111111111111111111"; +pub const SYSVAR_RENT: &str = "SysvarRent111111111111111111111111111111111"; + +/// `StakeInstruction::DelegateStake`, a bare u32 discriminant. +pub const DELEGATE_STAKE: [u8; 4] = [2, 0, 0, 0]; + +/// How many accounts `DelegateStake` takes. Bound into the member's proof, so a +/// settler can neither add one nor drop one. +pub const DELEGATE_ACCOUNTS: u8 = 6; + +/// `StakeStateV2` is 200 bytes whatever variant it holds. +pub const ACCOUNT_LEN: usize = 200; + +/// The variant a stake account only reaches by being delegated. +/// +/// An initialised but undelegated account is variant 1, so the discriminant +/// alone separates "the instruction landed" from "the delegation took" — which +/// is the difference between a signature and a result. +const STATE_STAKE: u32 = 2; + +/// Where `Delegation.voter_pubkey` sits inside a serialized `StakeStateV2`. +/// +/// Bincode, no padding: a u32 discriminant, then `Meta` — 8 bytes of rent +/// reserve, two 32-byte authorities, and a 48-byte `Lockup` of an i64, a u64 and +/// a 32-byte custodian — and then `Stake`, whose first field is `Delegation`, +/// whose first field is the vote account. 4 + 8 + 64 + 48 = 124. +const VOTER_OFFSET: usize = 124; + +pub fn program_id() -> Result { + parse(PROGRAM) +} + +fn parse(s: &str) -> Result { + s.parse().map_err(|e| anyhow!("{s}: {e}")) +} + +/// Creates a stake account and initialises its two authorities. +/// +/// The pair belongs together: an account created into the stake program and left +/// uninitialised is rent paid for nothing, and both halves fit in one +/// transaction, so they are returned as one unit rather than as two steps a +/// caller could interleave. +/// +/// The **staker** and the **withdrawer** are separate arguments and are meant to +/// be different keys. The pool's signature is available to every member, so an +/// authority the vault holds is an authority every member holds: delegation is +/// safe on those terms — the worst a member can do is re-delegate to another +/// validator — and withdrawal is not. +pub fn create( + payer: &Pubkey, + stake: &Pubkey, + staker: &Pubkey, + withdrawer: &Pubkey, + lamports: u64, +) -> Result<[Instruction; 2]> { + let program = program_id()?; + let create = solana_system_interface::instruction::create_account( + payer, + stake, + lamports, + ACCOUNT_LEN as u64, + &program, + ); + + // `StakeInstruction::Initialize { Authorized, Lockup }`: a u32 discriminant, + // the two authorities, then a zero lockup. + let mut data = 0u32.to_le_bytes().to_vec(); + data.extend_from_slice(&staker.to_bytes()); + data.extend_from_slice(&withdrawer.to_bytes()); + data.extend_from_slice(&0i64.to_le_bytes()); // lockup.unix_timestamp + data.extend_from_slice(&0u64.to_le_bytes()); // lockup.epoch + data.extend_from_slice(&[0u8; 32]); // lockup.custodian + let initialise = Instruction::new_with_bytes( + program, + &data, + vec![ + AccountMeta::new(*stake, false), + AccountMeta::new_readonly(parse(SYSVAR_RENT)?, false), + ], + ); + Ok([create, initialise]) +} + +/// The account list `DelegateStake` expects, in the callee's own order. +/// +/// Taken from what the `solana` CLI builds rather than from memory. The +/// authority sits in the last slot and the deprecated config account is still in +/// the list; a list that omits it puts the authority one slot early and the +/// stake program reports a missing signature for an account that did sign. +pub fn delegate_accounts( + stake: &Pubkey, + vote: &Pubkey, + authority: &Pubkey, +) -> Result> { + Ok(vec![ + AccountMeta::new(*stake, false), + AccountMeta::new_readonly(*vote, false), + AccountMeta::new_readonly(parse(SYSVAR_CLOCK)?, false), + AccountMeta::new_readonly(parse(SYSVAR_STAKE_HISTORY)?, false), + AccountMeta::new_readonly(parse(CONFIG)?, false), + AccountMeta::new_readonly(*authority, false), + ]) +} + +/// Which validator a stake account currently backs. +/// +/// Reads the answer out of the account rather than assuming it from what was +/// requested, so a delegation that landed against a different validator than the +/// member asked for is a caught error rather than an unnoticed one. +pub fn delegated_voter(data: &[u8]) -> Result { + if data.len() < VOTER_OFFSET + 32 { + return Err(anyhow!( + "the stake account is {} bytes, too short to be a stake state", + data.len() + )); + } + let discriminant = u32::from_le_bytes([data[0], data[1], data[2], data[3]]); + if discriminant != STATE_STAKE { + return Err(anyhow!( + "the stake account is in state {discriminant}, not Stake({STATE_STAKE}): \ + the delegation did not take" + )); + } + Ok(Pubkey::new_from_array( + data[VOTER_OFFSET..VOTER_OFFSET + 32] + .try_into() + .map_err(|_| anyhow!("reading the vote account"))?, + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn state(discriminant: u32, voter: &Pubkey) -> Vec { + let mut data = vec![0u8; ACCOUNT_LEN]; + data[..4].copy_from_slice(&discriminant.to_le_bytes()); + data[VOTER_OFFSET..VOTER_OFFSET + 32].copy_from_slice(&voter.to_bytes()); + data + } + + #[test] + fn a_delegated_account_reports_the_validator_it_backs() { + let voter = Pubkey::new_unique(); + assert_eq!(delegated_voter(&state(STATE_STAKE, &voter)).unwrap(), voter); + } + + /// The check that separates a signature from a result. An initialised but + /// undelegated account carries a plausible-looking key at the voter offset — + /// zeroes, here — and reading it without checking the variant would report a + /// delegation that never happened. + #[test] + fn an_initialised_but_undelegated_account_is_not_read_as_delegated() { + let err = delegated_voter(&state(1, &Pubkey::new_unique())) + .unwrap_err() + .to_string(); + assert!(err.contains("did not take"), "{err}"); + } + + #[test] + fn an_account_too_short_to_hold_a_delegation_is_refused() { + assert!(delegated_voter(&[0u8; 64]).is_err()); + } + + /// The order is the whole point of the function, and it is the one thing a + /// reader cannot check by looking at the callee from here. + #[test] + fn the_authority_is_the_last_account_and_the_vote_account_the_second() { + let stake = Pubkey::new_unique(); + let vote = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let metas = delegate_accounts(&stake, &vote, &authority).unwrap(); + assert_eq!(metas.len(), DELEGATE_ACCOUNTS as usize); + assert_eq!(metas[0].pubkey, stake); + assert!(metas[0].is_writable, "the stake account is written"); + assert_eq!(metas[1].pubkey, vote); + assert_eq!(metas[5].pubkey, authority); + assert!( + metas[1..].iter().all(|m| !m.is_writable), + "only the stake account is written" + ); + } + + /// The authority the pool holds must not be the authority that can take the + /// money out. Asserted on the instruction this crate builds, because the + /// argument order is the only thing standing between the two. + #[test] + fn the_staker_and_the_withdrawer_are_written_to_different_slots() { + let payer = Pubkey::new_unique(); + let stake = Pubkey::new_unique(); + let staker = Pubkey::new_unique(); + let withdrawer = Pubkey::new_unique(); + let [_, initialise] = create(&payer, &stake, &staker, &withdrawer, 1_000_000).unwrap(); + assert_eq!(&initialise.data[4..36], staker.to_bytes().as_slice()); + assert_eq!(&initialise.data[36..68], withdrawer.to_bytes().as_slice()); + } +} diff --git a/data/crowd-result-43000007.json b/data/crowd-result-43000007.json new file mode 100644 index 00000000..81d0a8a1 --- /dev/null +++ b/data/crowd-result-43000007.json @@ -0,0 +1,149 @@ +{ + "pool": "Cqg4gj4zwHZGfp1P2v6j6pB4dLbWgsL1vJB7YkjsFWAK", + "vault": "6NrJ3id157J7qMi5veukujtvX6T5rJz1WZT1m6gd8eMZ", + "denomination": 43000007, + "ceiling": { + "divergent": 6, + "uniform": 7, + "divergent_bytes": 1194, + "over_bytes": 1332, + "budget_bytes": 40 + }, + "roster": [ + { + "stake": "5P9AHY2tGoQ9xedLeyvC5WsU4FXxxH8LgtC2kzRUMZtT", + "vote": "vgcDar2pryHvMgPkKaZfh8pQy4BJxv7SpwUG7zinWjG", + "relay": "7GC9vjEJTUk1d3DLfb9ufBuSo4ooU5HzAmzeGdFejtBX" + }, + { + "stake": "DrrzXbAmLdgLfmcGtN1mAjgEuRK5mPyRSVcwazUtUWaT", + "vote": "i7NyKBMJCA9bLM2nsGyAGCKHECuR2L5eh4GqFciuwNT", + "relay": "CDNhhfH3qspD6Bgwsd6i7T6EGjE3r7yWPGBwaHyi5TXM" + }, + { + "stake": "EgGXn9y2ea9bDqGoUpU6Z8ET2JNm6zNaFu1Ux2x6RNNx", + "vote": "5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN", + "relay": "2UWeNWf8yg3UPbeycv6SMoKLjmbtD2XgAX8wiQ92RouF" + }, + { + "stake": "FgWdYn2gN9oUxN18NJ958BkgR5zoCFagWp73pQ1YHc9x", + "vote": "23AoPQc3EPkfLWb14cKiWNahh1H9rtb3UBk8gWseohjF", + "relay": "D1oCaiaGCJGyi2Co7mE7W5nMpcATdDnGtGMSGrk8Qcpw" + }, + { + "stake": "Fa8khDvR3arVezDmDZpLPHA4VVwRuSwzfJ2gcDSKQmDo", + "vote": "7AETLyAGJWjp6AWzZqZcP362yv5LQ3nLEdwnXNjdNwwF", + "relay": "DtoAEYygmCWw156gn4DB1ZCUTJ9szUbT7mLxqNs5bTos" + }, + { + "stake": "B5unNLodBUxcsggqV4g1KzoyAXz4oNHN3bEnRTJ1Fmkw", + "vote": "2u83Dx5qPV4QnujjJQv8v2SoqG1ixuAxPK5Jwhtkovd1", + "relay": "3dYe9vTAVYpsrSYtxLRrawFYQaMNQsLhqhscmFhRKobY" + } + ], + "signature": "5SqtqpuJw3pAoBMMB9DmXaqFr93rWsnqWMyacRZ3ZpP5eQpacLTAjrYugAW2ni3PMucAzDLJ4TsLZU3aQd3ZVMPZ", + "bytes": 1194, + "compute_units": 142856, + "steps": [ + { + "name": "init_pool", + "signature": "5dPVS1efe2JcqJxV26Lxk5QwnTdsy8qfxc6wbFJXKNM9ES16qx6gG7P53c9RLnuMkzswjM9yoW4JEScrwYYg4HSN", + "note": "denomination 43000007, k_floor 6" + }, + { + "name": "deposit", + "signature": "4eTFg35LP21D3HCQYn2DE7WExrhNZsmyfh39i7GMYe9bxfsyEhdJJ2STDt2c6SmaeEGAB1whQbRtqbr71kRoMfDr", + "note": "note 1" + }, + { + "name": "deposit", + "signature": "2BqmBu6anyCMhyvexeVsuH9zLB847bW1xBNndxeidCU7FYma3VyxcqTUEmRRVvJgH95MtTzsuEbwcRuxD4K25VQm", + "note": "note 2" + }, + { + "name": "deposit", + "signature": "7RwN4jc54ioVS7Y8m8p9PKGnU2vPrwPpK7jRZ4RvfmF4TWCniBxzNrxzbXpkFt3R6VWhfKnvP4d5sypGrCCXq2E", + "note": "note 3" + }, + { + "name": "deposit", + "signature": "3drHxm9YgHi4VUy45HRVbmtZQe76oaPF7dxYWh6VAXKiJhUG7uBscPuDhmZVzRdnmFHtvg72PR9qwMU4RizpKPiE", + "note": "note 4" + }, + { + "name": "deposit", + "signature": "2YB3RHGPCu3cY3tXjnRkFisd7q2JYZUnYB2z8hC11FHbX5YAsGjSiAs7BJbGTg4JHXE3FVwSVpz9t9NFevdQ5Gxh", + "note": "note 5" + }, + { + "name": "deposit", + "signature": "2UHgtUdLYJQmqBXcJWPJPK7VM6uBLKqaZdcMucXWoUUVpYV4Cnk8ox1jMUSZx9DJk6Xp6EKEQtxL9WLdWP4jT2ZL", + "note": "note 6" + }, + { + "name": "create stake account", + "signature": "28djx5Y9hNmg58XbTzd4if4N6H2kZUwfREBKtdrCsFnV6Gu6bb6ptrGtMzL6VoZnVYSFKRXQTDNcKHdLUiQoGHwq", + "note": "1100000000 lamports, staker = the pool's vault" + }, + { + "name": "submit_spend", + "signature": "5D6VC6czzJ79fzuvvXuNHX5xcV5p7Gbv6hxDwHzgt5QtHHKScu11hYSBkdPPe7kQoUhhQEBoyiDPzjWGGVnroyFT", + "note": "note 0, relay-signed, delegate to vgcDar2pryHvMgPkKaZfh8pQy4BJxv7SpwUG7zinWjG" + }, + { + "name": "create stake account", + "signature": "2Tv4CZSFD8wxpuhiihsw2xEd53bxTE7i6ka1dpZvzhcfDqXxdCL8k1jydgPCjJ7pUuL4CuPHP11BnJHPqjsawK6V", + "note": "1100000000 lamports, staker = the pool's vault" + }, + { + "name": "submit_spend", + "signature": "4gpxQ7UzGNKBwft1Y8dmDszs2jRz1S1Q2RZsFkLf4AjmyY1B3YNSyPoLPA5uqbRonWx2SkYZCYFtyS5AZN2EKsGK", + "note": "note 1, relay-signed, delegate to i7NyKBMJCA9bLM2nsGyAGCKHECuR2L5eh4GqFciuwNT" + }, + { + "name": "create stake account", + "signature": "3znGocR1LKMkffXQ1yHm9BKfhbM1o4Y6r22J3vyKjGLK76fAfArMxe7CeoP8sk4umStTt7f7EtSKbe29zaz4iges", + "note": "1100000000 lamports, staker = the pool's vault" + }, + { + "name": "submit_spend", + "signature": "2YBakbrJy1CXTaJpkxwAr54EW5PdWytG4P2ycJqFyRx48tdKKMqMuejEjS9JaMGmxGHKY9uiJ623tjb8ErDUFHS1", + "note": "note 2, relay-signed, delegate to 5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN" + }, + { + "name": "create stake account", + "signature": "MhASpmqysCWXNrS5B36RVopqKnQKFcSRanQBd1nueD2bCck4SQBvJEqYy98ajhb9JC8UcvvncjRQcghnDyXYqzf", + "note": "1100000000 lamports, staker = the pool's vault" + }, + { + "name": "submit_spend", + "signature": "uEZXvq615hGxgeKKYmrpP4YPp5B3xygRUNAgZ4ddCH47q6NypgQqR49B8U1vMB9XVWnZQaPjqcqbV1JeQgAKFvN", + "note": "note 3, relay-signed, delegate to 23AoPQc3EPkfLWb14cKiWNahh1H9rtb3UBk8gWseohjF" + }, + { + "name": "create stake account", + "signature": "3JYyJ1ZnuGUdX888cV4zkqmhqTd42zE8QAbo3x8jJ6bfnC4MZT2RkiQkLHnqtjkNJmTQSAeQDLbEVwWRXaMmS5ES", + "note": "1100000000 lamports, staker = the pool's vault" + }, + { + "name": "submit_spend", + "signature": "5KE1wc2VZh6GTLiiVNQ8Xad92hHoKNARZ8AEWQvwJeBsfLJ93Cc6GkvVtieTdwCh5ZjPcDTwk31Jjzwshtemr7XN", + "note": "note 4, relay-signed, delegate to 7AETLyAGJWjp6AWzZqZcP362yv5LQ3nLEdwnXNjdNwwF" + }, + { + "name": "create stake account", + "signature": "2fRNA4wLYzoJceT2HmAXEPrvK1Sv1haqfRJK7vDDcyKCLQPa65ebcE9u6Lm8DZbdgfWm2LiaS5nDUzvxaCNrwecE", + "note": "1100000000 lamports, staker = the pool's vault" + }, + { + "name": "submit_spend", + "signature": "4haLJna7fmp85ahKppvXR8bVgGSFmhsftxy1XEfuztZ8mKVKxdVzdwLVwS6CedD2fmzdMwPWc5Na2DAaNvUZdgQM", + "note": "note 5, relay-signed, delegate to 2u83Dx5qPV4QnujjJQv8v2SoqG1ixuAxPK5Jwhtkovd1" + }, + { + "name": "settle_epoch", + "signature": "5SqtqpuJw3pAoBMMB9DmXaqFr93rWsnqWMyacRZ3ZpP5eQpacLTAjrYugAW2ni3PMucAzDLJ4TsLZU3aQd3ZVMPZ", + "note": "6 delegations to 6 different validators, one transaction, 1194 bytes" + } + ] +} diff --git a/docs/CROWD.md b/docs/CROWD.md new file mode 100644 index 00000000..facd5385 --- /dev/null +++ b/docs/CROWD.md @@ -0,0 +1,85 @@ +# Different members, different validators, one timestamp + +A batch whose members all do the same thing is the easy case for an anonymity set: there is nothing to tell them apart to begin with. This run does the hard one. Each member delegates stake to a **different validator**, and all of it settles in a single transaction. + +Validator choice is the behavioural pattern worth hiding. It is stable, it is public, and it fingerprints a staker across epochs far more reliably than an amount does. What an observer gets from the transaction below is a set of delegations landing at one timestamp with no way to say which member asked for which. + +Generated by `mirror crowd`. Pool [`Cqg4gj4zwHZGfp1P2v6j6pB4dLbWgsL1vJB7YkjsFWAK`](https://explorer.solana.com/address/Cqg4gj4zwHZGfp1P2v6j6pB4dLbWgsL1vJB7YkjsFWAK?cluster=devnet), vault [`6NrJ3id157J7qMi5veukujtvX6T5rJz1WZT1m6gd8eMZ`](https://explorer.solana.com/address/6NrJ3id157J7qMi5veukujtvX6T5rJz1WZT1m6gd8eMZ?cluster=devnet), denomination 43000007 lamports. + +## The settlement + +[`5SqtqpuJw3pAoBMMB9DmXaqFr93rWsnqWMyacRZ3ZpP5eQpacLTAjrYugAW2ni3PMucAzDLJ4TsLZU3aQd3ZVMPZ`](https://explorer.solana.com/tx/5SqtqpuJw3pAoBMMB9DmXaqFr93rWsnqWMyacRZ3ZpP5eQpacLTAjrYugAW2ni3PMucAzDLJ4TsLZU3aQd3ZVMPZ?cluster=devnet) + +| | | +|---|---| +| members | 6 | +| distinct validators | 6 | +| wire size | 1194 of 1232 bytes | +| compute | 142856 of 200000 CU | + +## Who got what + +Read back from the cluster after settlement, not assumed from what was requested. A member's proof binds how many accounts their call takes and never *which*, so the stake account's own state is the only place the answer exists — this table is the check that limit needs, and the run fails if any row disagrees. + +Every member has their own relay, and no relay key appears twice. That is the worst case for the packet and the case a settler has to plan against: a batch whose members shared a relay would name fewer distinct keys and fit more of them. + +| member | stake account | delegated to | relay | +|---|---|---|---| +| 0 | [`5P9AHY2tGoQ9xedLeyvC5WsU4FXxxH8LgtC2kzRUMZtT`](https://explorer.solana.com/address/5P9AHY2tGoQ9xedLeyvC5WsU4FXxxH8LgtC2kzRUMZtT?cluster=devnet) | [`vgcDar2pryHvMgPkKaZfh8pQy4BJxv7SpwUG7zinWjG`](https://explorer.solana.com/address/vgcDar2pryHvMgPkKaZfh8pQy4BJxv7SpwUG7zinWjG?cluster=devnet) | [`7GC9vjEJTUk1d3DLfb9ufBuSo4ooU5HzAmzeGdFejtBX`](https://explorer.solana.com/address/7GC9vjEJTUk1d3DLfb9ufBuSo4ooU5HzAmzeGdFejtBX?cluster=devnet) | +| 1 | [`DrrzXbAmLdgLfmcGtN1mAjgEuRK5mPyRSVcwazUtUWaT`](https://explorer.solana.com/address/DrrzXbAmLdgLfmcGtN1mAjgEuRK5mPyRSVcwazUtUWaT?cluster=devnet) | [`i7NyKBMJCA9bLM2nsGyAGCKHECuR2L5eh4GqFciuwNT`](https://explorer.solana.com/address/i7NyKBMJCA9bLM2nsGyAGCKHECuR2L5eh4GqFciuwNT?cluster=devnet) | [`CDNhhfH3qspD6Bgwsd6i7T6EGjE3r7yWPGBwaHyi5TXM`](https://explorer.solana.com/address/CDNhhfH3qspD6Bgwsd6i7T6EGjE3r7yWPGBwaHyi5TXM?cluster=devnet) | +| 2 | [`EgGXn9y2ea9bDqGoUpU6Z8ET2JNm6zNaFu1Ux2x6RNNx`](https://explorer.solana.com/address/EgGXn9y2ea9bDqGoUpU6Z8ET2JNm6zNaFu1Ux2x6RNNx?cluster=devnet) | [`5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN`](https://explorer.solana.com/address/5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN?cluster=devnet) | [`2UWeNWf8yg3UPbeycv6SMoKLjmbtD2XgAX8wiQ92RouF`](https://explorer.solana.com/address/2UWeNWf8yg3UPbeycv6SMoKLjmbtD2XgAX8wiQ92RouF?cluster=devnet) | +| 3 | [`FgWdYn2gN9oUxN18NJ958BkgR5zoCFagWp73pQ1YHc9x`](https://explorer.solana.com/address/FgWdYn2gN9oUxN18NJ958BkgR5zoCFagWp73pQ1YHc9x?cluster=devnet) | [`23AoPQc3EPkfLWb14cKiWNahh1H9rtb3UBk8gWseohjF`](https://explorer.solana.com/address/23AoPQc3EPkfLWb14cKiWNahh1H9rtb3UBk8gWseohjF?cluster=devnet) | [`D1oCaiaGCJGyi2Co7mE7W5nMpcATdDnGtGMSGrk8Qcpw`](https://explorer.solana.com/address/D1oCaiaGCJGyi2Co7mE7W5nMpcATdDnGtGMSGrk8Qcpw?cluster=devnet) | +| 4 | [`Fa8khDvR3arVezDmDZpLPHA4VVwRuSwzfJ2gcDSKQmDo`](https://explorer.solana.com/address/Fa8khDvR3arVezDmDZpLPHA4VVwRuSwzfJ2gcDSKQmDo?cluster=devnet) | [`7AETLyAGJWjp6AWzZqZcP362yv5LQ3nLEdwnXNjdNwwF`](https://explorer.solana.com/address/7AETLyAGJWjp6AWzZqZcP362yv5LQ3nLEdwnXNjdNwwF?cluster=devnet) | [`DtoAEYygmCWw156gn4DB1ZCUTJ9szUbT7mLxqNs5bTos`](https://explorer.solana.com/address/DtoAEYygmCWw156gn4DB1ZCUTJ9szUbT7mLxqNs5bTos?cluster=devnet) | +| 5 | [`B5unNLodBUxcsggqV4g1KzoyAXz4oNHN3bEnRTJ1Fmkw`](https://explorer.solana.com/address/B5unNLodBUxcsggqV4g1KzoyAXz4oNHN3bEnRTJ1Fmkw?cluster=devnet) | [`2u83Dx5qPV4QnujjJQv8v2SoqG1ixuAxPK5Jwhtkovd1`](https://explorer.solana.com/address/2u83Dx5qPV4QnujjJQv8v2SoqG1ixuAxPK5Jwhtkovd1?cluster=devnet) | [`3dYe9vTAVYpsrSYtxLRrawFYQaMNQsLhqhscmFhRKobY`](https://explorer.solana.com/address/3dYe9vTAVYpsrSYtxLRrawFYQaMNQsLhqhscmFhRKobY?cluster=devnet) | + +## What divergence costs + +Divergence is not free, and the price is anonymity-set size. A transaction names each distinct account once, so a member who picks their own validator adds a vote account nobody else in the batch names. The 1232-byte packet is reached sooner. + +| batch | members per settlement | +|---|---| +| all delegating to the same validator | 7 | +| each delegating to a different one | 6 | + +At 6 members the settlement weighs 1194 bytes with 38 to spare; one more member weighs 1332 bytes, 100 over the limit. Both figures come from serializing the real instruction, and the settlement that landed above is 1194 bytes — the same number, which is what makes the measurement a prediction rather than a description. + +## And compute, which is closer than it is for payments + +This settlement burned **142856 CU of the 200000** a single instruction gets by default — 71% of the budget, for 6 payouts and 6 cross-program invocations. That is a different regime from a batch of plain transfers: `ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_the_eleventh` settles ten of those in 19,545 CU, where compute is nowhere in the conversation. A delegation costs roughly an order of magnitude more per member than a payment does. + +Each member's Groth16 proof was verified earlier, in their own `submit_spend`, which is measured at 101,127 CU. That is why the two phases exist: verifying 6 proofs here would cost over 600,000 CU — comfortably past the 200,000 a single instruction gets by default, and a large fraction of the 1.4M a whole transaction may ever request. + +The packet still binds first — 6 members is where the bytes run out, and the budget is not exhausted there — but the two limits are no longer independent, and that is the part worth stating plainly. The usual answer to a settlement that runs out of compute is to ask for more with a `SetComputeUnitLimit` instruction. Measured against this very batch, that instruction costs **40 bytes**, and a full settlement has 38 to spare. **Raising the budget means dropping a member.** + +So the ceiling here is not a byte count that a future compute optimisation would lift. It is the point where the only two exits are closed at once, and the answer to a larger crowd is more transactions — which means more timestamps, which is a real cost to the anonymity and the reason this number is worth knowing. + +## Every step + +| step | signature | note | +|---|---|---| +| init_pool | [`5dPVS1efe2JcqJxV26Lxk5QwnTdsy8qfxc6wbFJXKNM9ES16qx6gG7P53c9RLnuMkzswjM9yoW4JEScrwYYg4HSN`](https://explorer.solana.com/tx/5dPVS1efe2JcqJxV26Lxk5QwnTdsy8qfxc6wbFJXKNM9ES16qx6gG7P53c9RLnuMkzswjM9yoW4JEScrwYYg4HSN?cluster=devnet) | denomination 43000007, k_floor 6 | +| deposit | [`4eTFg35LP21D3HCQYn2DE7WExrhNZsmyfh39i7GMYe9bxfsyEhdJJ2STDt2c6SmaeEGAB1whQbRtqbr71kRoMfDr`](https://explorer.solana.com/tx/4eTFg35LP21D3HCQYn2DE7WExrhNZsmyfh39i7GMYe9bxfsyEhdJJ2STDt2c6SmaeEGAB1whQbRtqbr71kRoMfDr?cluster=devnet) | note 1 | +| deposit | [`2BqmBu6anyCMhyvexeVsuH9zLB847bW1xBNndxeidCU7FYma3VyxcqTUEmRRVvJgH95MtTzsuEbwcRuxD4K25VQm`](https://explorer.solana.com/tx/2BqmBu6anyCMhyvexeVsuH9zLB847bW1xBNndxeidCU7FYma3VyxcqTUEmRRVvJgH95MtTzsuEbwcRuxD4K25VQm?cluster=devnet) | note 2 | +| deposit | [`7RwN4jc54ioVS7Y8m8p9PKGnU2vPrwPpK7jRZ4RvfmF4TWCniBxzNrxzbXpkFt3R6VWhfKnvP4d5sypGrCCXq2E`](https://explorer.solana.com/tx/7RwN4jc54ioVS7Y8m8p9PKGnU2vPrwPpK7jRZ4RvfmF4TWCniBxzNrxzbXpkFt3R6VWhfKnvP4d5sypGrCCXq2E?cluster=devnet) | note 3 | +| deposit | [`3drHxm9YgHi4VUy45HRVbmtZQe76oaPF7dxYWh6VAXKiJhUG7uBscPuDhmZVzRdnmFHtvg72PR9qwMU4RizpKPiE`](https://explorer.solana.com/tx/3drHxm9YgHi4VUy45HRVbmtZQe76oaPF7dxYWh6VAXKiJhUG7uBscPuDhmZVzRdnmFHtvg72PR9qwMU4RizpKPiE?cluster=devnet) | note 4 | +| deposit | [`2YB3RHGPCu3cY3tXjnRkFisd7q2JYZUnYB2z8hC11FHbX5YAsGjSiAs7BJbGTg4JHXE3FVwSVpz9t9NFevdQ5Gxh`](https://explorer.solana.com/tx/2YB3RHGPCu3cY3tXjnRkFisd7q2JYZUnYB2z8hC11FHbX5YAsGjSiAs7BJbGTg4JHXE3FVwSVpz9t9NFevdQ5Gxh?cluster=devnet) | note 5 | +| deposit | [`2UHgtUdLYJQmqBXcJWPJPK7VM6uBLKqaZdcMucXWoUUVpYV4Cnk8ox1jMUSZx9DJk6Xp6EKEQtxL9WLdWP4jT2ZL`](https://explorer.solana.com/tx/2UHgtUdLYJQmqBXcJWPJPK7VM6uBLKqaZdcMucXWoUUVpYV4Cnk8ox1jMUSZx9DJk6Xp6EKEQtxL9WLdWP4jT2ZL?cluster=devnet) | note 6 | +| create stake account | [`28djx5Y9hNmg58XbTzd4if4N6H2kZUwfREBKtdrCsFnV6Gu6bb6ptrGtMzL6VoZnVYSFKRXQTDNcKHdLUiQoGHwq`](https://explorer.solana.com/tx/28djx5Y9hNmg58XbTzd4if4N6H2kZUwfREBKtdrCsFnV6Gu6bb6ptrGtMzL6VoZnVYSFKRXQTDNcKHdLUiQoGHwq?cluster=devnet) | 1100000000 lamports, staker = the pool's vault | +| submit_spend | [`5D6VC6czzJ79fzuvvXuNHX5xcV5p7Gbv6hxDwHzgt5QtHHKScu11hYSBkdPPe7kQoUhhQEBoyiDPzjWGGVnroyFT`](https://explorer.solana.com/tx/5D6VC6czzJ79fzuvvXuNHX5xcV5p7Gbv6hxDwHzgt5QtHHKScu11hYSBkdPPe7kQoUhhQEBoyiDPzjWGGVnroyFT?cluster=devnet) | note 0, relay-signed, delegate to vgcDar2pryHvMgPkKaZfh8pQy4BJxv7SpwUG7zinWjG | +| create stake account | [`2Tv4CZSFD8wxpuhiihsw2xEd53bxTE7i6ka1dpZvzhcfDqXxdCL8k1jydgPCjJ7pUuL4CuPHP11BnJHPqjsawK6V`](https://explorer.solana.com/tx/2Tv4CZSFD8wxpuhiihsw2xEd53bxTE7i6ka1dpZvzhcfDqXxdCL8k1jydgPCjJ7pUuL4CuPHP11BnJHPqjsawK6V?cluster=devnet) | 1100000000 lamports, staker = the pool's vault | +| submit_spend | [`4gpxQ7UzGNKBwft1Y8dmDszs2jRz1S1Q2RZsFkLf4AjmyY1B3YNSyPoLPA5uqbRonWx2SkYZCYFtyS5AZN2EKsGK`](https://explorer.solana.com/tx/4gpxQ7UzGNKBwft1Y8dmDszs2jRz1S1Q2RZsFkLf4AjmyY1B3YNSyPoLPA5uqbRonWx2SkYZCYFtyS5AZN2EKsGK?cluster=devnet) | note 1, relay-signed, delegate to i7NyKBMJCA9bLM2nsGyAGCKHECuR2L5eh4GqFciuwNT | +| create stake account | [`3znGocR1LKMkffXQ1yHm9BKfhbM1o4Y6r22J3vyKjGLK76fAfArMxe7CeoP8sk4umStTt7f7EtSKbe29zaz4iges`](https://explorer.solana.com/tx/3znGocR1LKMkffXQ1yHm9BKfhbM1o4Y6r22J3vyKjGLK76fAfArMxe7CeoP8sk4umStTt7f7EtSKbe29zaz4iges?cluster=devnet) | 1100000000 lamports, staker = the pool's vault | +| submit_spend | [`2YBakbrJy1CXTaJpkxwAr54EW5PdWytG4P2ycJqFyRx48tdKKMqMuejEjS9JaMGmxGHKY9uiJ623tjb8ErDUFHS1`](https://explorer.solana.com/tx/2YBakbrJy1CXTaJpkxwAr54EW5PdWytG4P2ycJqFyRx48tdKKMqMuejEjS9JaMGmxGHKY9uiJ623tjb8ErDUFHS1?cluster=devnet) | note 2, relay-signed, delegate to 5ZWgXcyqrrNpQHCme5SdC5hCeYb2o3fEJhF7Gok3bTVN | +| create stake account | [`MhASpmqysCWXNrS5B36RVopqKnQKFcSRanQBd1nueD2bCck4SQBvJEqYy98ajhb9JC8UcvvncjRQcghnDyXYqzf`](https://explorer.solana.com/tx/MhASpmqysCWXNrS5B36RVopqKnQKFcSRanQBd1nueD2bCck4SQBvJEqYy98ajhb9JC8UcvvncjRQcghnDyXYqzf?cluster=devnet) | 1100000000 lamports, staker = the pool's vault | +| submit_spend | [`uEZXvq615hGxgeKKYmrpP4YPp5B3xygRUNAgZ4ddCH47q6NypgQqR49B8U1vMB9XVWnZQaPjqcqbV1JeQgAKFvN`](https://explorer.solana.com/tx/uEZXvq615hGxgeKKYmrpP4YPp5B3xygRUNAgZ4ddCH47q6NypgQqR49B8U1vMB9XVWnZQaPjqcqbV1JeQgAKFvN?cluster=devnet) | note 3, relay-signed, delegate to 23AoPQc3EPkfLWb14cKiWNahh1H9rtb3UBk8gWseohjF | +| create stake account | [`3JYyJ1ZnuGUdX888cV4zkqmhqTd42zE8QAbo3x8jJ6bfnC4MZT2RkiQkLHnqtjkNJmTQSAeQDLbEVwWRXaMmS5ES`](https://explorer.solana.com/tx/3JYyJ1ZnuGUdX888cV4zkqmhqTd42zE8QAbo3x8jJ6bfnC4MZT2RkiQkLHnqtjkNJmTQSAeQDLbEVwWRXaMmS5ES?cluster=devnet) | 1100000000 lamports, staker = the pool's vault | +| submit_spend | [`5KE1wc2VZh6GTLiiVNQ8Xad92hHoKNARZ8AEWQvwJeBsfLJ93Cc6GkvVtieTdwCh5ZjPcDTwk31Jjzwshtemr7XN`](https://explorer.solana.com/tx/5KE1wc2VZh6GTLiiVNQ8Xad92hHoKNARZ8AEWQvwJeBsfLJ93Cc6GkvVtieTdwCh5ZjPcDTwk31Jjzwshtemr7XN?cluster=devnet) | note 4, relay-signed, delegate to 7AETLyAGJWjp6AWzZqZcP362yv5LQ3nLEdwnXNjdNwwF | +| create stake account | [`2fRNA4wLYzoJceT2HmAXEPrvK1Sv1haqfRJK7vDDcyKCLQPa65ebcE9u6Lm8DZbdgfWm2LiaS5nDUzvxaCNrwecE`](https://explorer.solana.com/tx/2fRNA4wLYzoJceT2HmAXEPrvK1Sv1haqfRJK7vDDcyKCLQPa65ebcE9u6Lm8DZbdgfWm2LiaS5nDUzvxaCNrwecE?cluster=devnet) | 1100000000 lamports, staker = the pool's vault | +| submit_spend | [`4haLJna7fmp85ahKppvXR8bVgGSFmhsftxy1XEfuztZ8mKVKxdVzdwLVwS6CedD2fmzdMwPWc5Na2DAaNvUZdgQM`](https://explorer.solana.com/tx/4haLJna7fmp85ahKppvXR8bVgGSFmhsftxy1XEfuztZ8mKVKxdVzdwLVwS6CedD2fmzdMwPWc5Na2DAaNvUZdgQM?cluster=devnet) | note 5, relay-signed, delegate to 2u83Dx5qPV4QnujjJQv8v2SoqG1ixuAxPK5Jwhtkovd1 | +| settle_epoch | [`5SqtqpuJw3pAoBMMB9DmXaqFr93rWsnqWMyacRZ3ZpP5eQpacLTAjrYugAW2ni3PMucAzDLJ4TsLZU3aQd3ZVMPZ`](https://explorer.solana.com/tx/5SqtqpuJw3pAoBMMB9DmXaqFr93rWsnqWMyacRZ3ZpP5eQpacLTAjrYugAW2ni3PMucAzDLJ4TsLZU3aQd3ZVMPZ?cluster=devnet) | 6 delegations to 6 different validators, one transaction, 1194 bytes | + +## Scope + +Devnet, and one operator. This is a functional and quantitative result, not an anonymity claim about a live crowd: the relays here were funded from the same wallet that made the deposits, which is exactly the linkage `USAGE.md` tells a real member to avoid. What the run establishes is that a batch of *divergent* actions settles as one, that every member reached the validator they chose, and what such a batch costs in packet space. + +The withdraw authority on every stake account above is the operator, never the pool. The pool's signature is available to every member, so an authority the vault holds is an authority every member holds: delegation is safe on those terms — the worst a member can do is re-delegate somebody else's stake — and withdrawal is not. That is `THREAT_MODEL.md` applied rather than repeated. diff --git a/docs/PROOF.md b/docs/PROOF.md index d1de46f2..5fb47aec 100644 --- a/docs/PROOF.md +++ b/docs/PROOF.md @@ -45,7 +45,7 @@ Paid out equals owed, and the vault came to rest on its floor with a remainder o ## The pool signed an action, and the callee said so -The settlement above carried four spends, and one of them was not a transfer: the pool invoked SPL Memo as that member's **authority**, in the same transaction as the other three. That is the capability a stake delegation or a governance vote needs and a payment does not. +The settlement above carried 5 spends, and 2 of them were not a transfer: the pool invoked SPL Memo as one member's **authority**, in the same transaction as the other 4. That is the capability a stake delegation or a governance vote needs and a payment does not. A signature only proves the transaction landed. It says nothing about who signed the instruction the pool made *inside* it, so the evidence has to come from the callee. SPL Memo refuses any account handed to it that has not signed, and names the ones that did: diff --git a/docs/USAGE.md b/docs/USAGE.md index 15af8d21..19b9d4f2 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -169,6 +169,15 @@ pool acts as your authority rather than only as your funder. That is what a stak delegation or a governance vote needs and a payment does not; `PROOF.md` has a real stake delegation done this way on devnet. +A word on what the proof does and does not promise here. It binds the selector, +the target program, the beneficiary, the fee, the payload and the **number** of +accounts — not which accounts fill the slots. For `DelegateStake` the validator +lives in an account slot rather than in the payload, so *the settler chooses your +validator*, and nothing on chain records which one you asked for. Check the +result: read the stake account back and see who it backs. `CROWD.md` is a devnet +run of six members delegating to six different validators that does exactly +that, for every member. + ## 6. Settle Permissionless. Anyone can settle, so no operator's absence can strand you, and @@ -213,21 +222,40 @@ never *which*, so no tool can infer the account list its callee expects. ## How many members settle together -Ten spends fit in one settlement transaction. The limit is the 1232-byte packet, -not compute: ten uses 1228 bytes and 19,545 of the 200,000 compute units a single -instruction gets by default. - -Each spend brings three accounts nobody else shares — its record, its beneficiary -and its relay — so the transaction grows about 99 bytes per member. A batch whose -members shared a relay names fewer distinct keys and fits more. +It depends on what the members are doing: -This is a ceiling per *transaction*, not per epoch. Settlement is permissionless, -so a pool with thirty pending spends settles in three batches; the cost is three -timestamps rather than one, which is a real cost to the anonymity and the reason -the number is worth knowing. - -`ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_the_eleventh` -pins it. +| the batch | members per settlement | +|---|---| +| plain payments | 10 | +| stake delegations, everyone to the same validator | 7 | +| stake delegations, a different validator each | 6 | + +The limit is the 1232-byte packet in every row. Each spend brings accounts +nobody else shares — its record, its beneficiary, its relay — so a payment costs +about 99 bytes per member, and a call costs more because it also names its +callee and the callee's accounts. + +**What you ask for changes how many people you can hide among.** A member who +delegates to their own choice of validator adds a vote account nobody else in +the batch names, and the batch loses a member. That is worth knowing before you +choose: a crowd that converges on one validator is both larger and less +distinguishable than a crowd that does not. + +For payments compute is nowhere near binding — ten settle in 19,545 of the +200,000 compute units a single instruction gets. For delegations it is much +closer: on devnet the six-member divergent batch used 142,856. And at that +ceiling there is no way out, because the two limits shut together — asking for a +larger compute budget costs a second instruction worth 40 bytes, and the batch +has 38 to spare. + +These are ceilings per *transaction*, not per epoch. Settlement is +permissionless, so a pool with thirty pending spends settles in three batches; +the cost is three timestamps rather than one, which is a real cost to the +anonymity and the reason the numbers are worth knowing. + +`ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_the_eleventh` and +`every_account_an_action_names_costs_the_batch_a_member` pin them, and +`CROWD.md` is a live devnet settlement of the six-member divergent case. ## Escaping without a relay diff --git a/programs/mirror-pool/tests/action_ceiling.rs b/programs/mirror-pool/tests/action_ceiling.rs new file mode 100644 index 00000000..982506ac --- /dev/null +++ b/programs/mirror-pool/tests/action_ceiling.rs @@ -0,0 +1,1041 @@ +//! What an action costs the crowd, measured against the compiled program. +//! +//! `batch_ceiling.rs` establishes that ten spends fit in one settlement and that +//! the 1232-byte packet, not compute, is what stops the eleventh. That number is +//! for plain transfers, where a spend brings exactly three accounts nobody else +//! shares: its record, its beneficiary and its relay. A spend whose action is a +//! CPI brings more — the callee's own program account, plus one slot per account +//! the action needs — so the ceiling for an action batch is lower, and until this +//! file nobody had taken it. +//! +//! The ceiling is not one number, because it is not a property of the action's +//! *shape* alone. Transaction size is driven by how many **distinct** keys the +//! message names, and two members delegating to the same validator name one vote +//! account between them while two members delegating to different validators name +//! two. So a batch whose members agree carries more members than a batch whose +//! members diverge: divergence in the content of an action is paid for in +//! anonymity-set size. That is the result this file is named for, and the two +//! stake batches below are settled for real rather than argued. +//! +//! Real means real. litesvm carries the Core BPF stake program, so a member's +//! `DelegateStake` here is executed by the actual stake program against a vote +//! account the actual vote program initialised, with the pool's vault signing as +//! the stake authority through `invoke_signed`. The batches settle, the stake +//! accounts end up delegated, and the compute the settlement consumed is +//! reported next to the bytes — because a size-only claim cannot say whether +//! compute also binds. +//! +//! Not every row is executed, and the table says which are. The two +//! stake-delegation rows — the result — are settled at their ceiling. The generic +//! curve around them (a call with k action accounts, shared between the members +//! or one each) is a size measurement and nothing more: it is taken from the +//! program's real instruction encoding rather than from arithmetic, but no batch +//! of that shape was run, so it is labelled `size only` and carries no compute +//! figure. The transfer row is the control: `batch_ceiling.rs` already settles it +//! against the same `.so`, so this file recomputing 10 spends / 1228 bytes is a +//! check on the encoder rather than a new claim. If that row ever moves, the +//! model of the wire here is wrong and every other number it prints is suspect. +//! +//! Requires `make build-sbf` first, like the rest of the on-chain suite. + +use litesvm::LiteSVM; +use mirror_circuit::{generate_reproducible, prove, Keys, SolanaProof, Witness}; +use mirror_core::{Field, MerkleTree, Note}; +use mirror_pool_program::{ + instruction::Instruction as MirrorIx, + pda::{pool_address, spend_address, vault_address}, + Pool, +}; +use solana_keypair::Keypair; +use solana_message::Message; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; +use solana_signer::Signer; +use solana_transaction::Transaction; + +const SEED: &[u8] = b"mirror-pool-reproducible-dev-setup-v1"; +const DENOMINATION: u64 = 100_000_000; // 0.1 SOL +const ENTRY_FEE: u64 = 0; +const K_FLOOR: u32 = 4; +const RELAY_FEE: u64 = 1_000_000; +const INVOKE_SIGNED: u64 = mirror_pool_program::processor::SELECTOR_INVOKE_SIGNED; + +/// The 1280-byte IPv6 minimum MTU, less a 40-byte IPv6 header and an 8-byte +/// fragment header. Restated here for the same reason `batch_ceiling.rs` restates +/// it: no crate this test already depends on re-exports +/// `solana_packet::PACKET_DATA_SIZE`, and a derivation a reader can check is +/// worth more than a constant they have to trust. +const PACKET_DATA_SIZE: usize = 1280 - 40 - 8; + +/// What a transaction carrying a single non-budget instruction gets unless it +/// asks for more. Asking costs a second instruction, and at these batch sizes +/// there are no bytes to spare for one. +const DEFAULT_COMPUTE_BUDGET: u64 = 200_000; + +// The addresses of the programs and sysvars a stake delegation names. Written as +// strings because these are cluster constants rather than anything this +// repository derives, and because `solana-program` 4.0 no longer re-exports the +// stake and vote ids at all. +const STAKE_PROGRAM: &str = "Stake11111111111111111111111111111111111111"; +const VOTE_PROGRAM: &str = "Vote111111111111111111111111111111111111111"; +/// Unused by the current stake program and still required in the account list at +/// the position `DelegateStake` has always put it. It costs a slot either way, +/// which is the only reason this measurement cares. +const STAKE_CONFIG: &str = "StakeConfig11111111111111111111111111111111"; +const CLOCK_SYSVAR: &str = "SysvarC1ock11111111111111111111111111111111"; +const STAKE_HISTORY_SYSVAR: &str = "SysvarStakeHistory1111111111111111111111111"; +const RENT_SYSVAR: &str = "SysvarRent111111111111111111111111111111111"; + +/// `VoteStateV3::size_of()`, and `VoteStateV4` deliberately matches it. +const VOTE_STATE_LEN: usize = 3762; +/// `StakeStateV2::size_of()`. +const STAKE_STATE_LEN: usize = 200; + +/// `StakeInstruction::DelegateStake`, bincode-encoded: a unit variant at index +/// two of the enum, which is a four-byte little-endian discriminant and nothing +/// else. This is the entire payload a member commits to when they delegate — the +/// validator is chosen by an *account*, not by the instruction data, which is +/// exactly why divergent validators cost keys rather than bytes. +const DELEGATE_STAKE: [u8; 4] = [2, 0, 0, 0]; + +/// What each stake account holds before the pool delegates it. +/// +/// It has to be pre-funded, and the reason is the selector rather than the test +/// rig: `SELECTOR_INVOKE_SIGNED` pays the beneficiary *after* the call so that +/// the vault's lamports are untouched at the moment it signs, so the amount the +/// stake program sees is whatever the account already held. The member's payout +/// lands on top of the delegation afterwards. One SOL clears the stake program's +/// minimum delegation with room to spare. +const PRE_STAKED: u64 = 1_000_000_000; + +// --------------------------------------------------------------------------- +// The answers, pinned +// +// Produced by the tests below and written back here. A fourth account per spend, +// or a stake delegation that grows a slot, must fail a test rather than quietly +// shrink the crowd a single settlement can carry. +// --------------------------------------------------------------------------- + +/// The plain-transfer ceiling, recomputed here from the encoder and cross-checked +/// against the executed measurement in `batch_ceiling.rs`. +const TRANSFER_CEILING: usize = 10; +const TRANSFER_BYTES: usize = 1228; + +/// A batch of members all delegating to the **same** validator. Per spend the +/// distinct keys are the record, the stake account and the relay; the vote +/// account, the two sysvars, the config account, the stake program and the pool's +/// vault are named once for the whole batch. +const SAME_VALIDATOR_CEILING: usize = 7; +const SAME_VALIDATOR_BYTES: usize = 1140; + +/// The same delegation where every member picked their own validator. One more +/// distinct key per spend, and the crowd loses a member. +const DIFFERENT_VALIDATOR_CEILING: usize = 6; +const DIFFERENT_VALIDATOR_BYTES: usize = 1194; + +fn key(s: &str) -> Pubkey { + s.parse().expect("a valid base58 address") +} + +fn program_bytes() -> Vec { + let path = concat!( + env!("CARGO_MANIFEST_DIR"), + "/../../target/sbf/deploy/mirror_pool_program.so" + ); + std::fs::read(path).unwrap_or_else(|e| { + panic!("could not read {path}: {e}\n\nRun `make build-sbf` first."); + }) +} + +// --------------------------------------------------------------------------- +// The wire +// --------------------------------------------------------------------------- + +/// The three accounts every settlement names before the batch begins. +/// +/// Split out from the SVM because transaction size is a pure function of the +/// message, so the sweeps below cost nothing to run and the real settlements can +/// be checked against them. +#[derive(Clone, Copy)] +struct Wire { + program_id: Pubkey, + pool: Pubkey, + vault: Pubkey, +} + +impl Wire { + fn new(program_id: Pubkey) -> Self { + let (pool, _) = pool_address(&program_id, DENOMINATION); + let (vault, _) = vault_address(&program_id, &pool); + Wire { + program_id, + pool, + vault, + } + } +} + +/// One spend in the order `settle_epoch` walks it: the record, the beneficiary +/// the proof bound, the relay that submitted it, then — for a CPI selector — the +/// callee's own program account followed by exactly the accounts the action +/// declared. The target precedes the action accounts because the handler reads it +/// first; getting that order wrong would measure a transaction the program cannot +/// execute. +struct Spend { + record: Pubkey, + beneficiary: Pubkey, + relay: Pubkey, + target: Option, + action: Vec, +} + +fn settle_ix(wire: &Wire, batch: &[Spend], settler: &Pubkey) -> Instruction { + let mut metas = vec![ + AccountMeta::new(*settler, true), + AccountMeta::new(wire.pool, false), + AccountMeta::new(wire.vault, false), + ]; + for spend in batch { + metas.push(AccountMeta::new(spend.record, false)); + metas.push(AccountMeta::new(spend.beneficiary, false)); + metas.push(AccountMeta::new(spend.relay, false)); + if let Some(target) = spend.target { + metas.push(AccountMeta::new_readonly(target, false)); + } + metas.extend(spend.action.iter().cloned()); + } + Instruction::new_with_bytes( + wire.program_id, + &MirrorIx::SettleEpoch { + count: batch.len() as u8, + } + .pack(), + metas, + ) +} + +fn settle_tx(svm: &LiteSVM, wire: &Wire, batch: &[Spend], settler: &Keypair) -> Transaction { + let ix = settle_ix(wire, batch, &settler.pubkey()); + let msg = Message::new(&[ix], Some(&settler.pubkey())); + Transaction::new(&[settler], msg, svm.latest_blockhash()) +} + +/// What this transaction weighs on the wire. +/// +/// A legacy transaction is a compact array of 64-byte signatures followed by the +/// serialized message, and `Message::serialize` is what a validator receives +/// rather than an approximation of it. The single-byte length prefix is asserted +/// rather than assumed: a second signer would move every number in this file by +/// 65 bytes without changing anything visible at the call site. +fn wire_len(tx: &Transaction) -> usize { + assert_eq!( + tx.signatures.len(), + 1, + "the framing here holds for a single signature" + ); + 1 + 64 * tx.signatures.len() + tx.message.serialize().len() +} + +// --------------------------------------------------------------------------- +// Shapes +// --------------------------------------------------------------------------- + +/// Where the key in an action's account slot comes from, which is the only thing +/// about that slot the encoding cares about. +/// +/// A slot always costs one byte in the instruction's account-index list. Whether +/// it also costs 32 bytes of key depends entirely on whether the batch has named +/// that account already — which is why `Shared`, `Vault` and `Beneficiary` are +/// distinguished from `Unique` at all. +#[derive(Clone, Copy)] +enum Slot { + /// A key belonging to this spend alone: a second member's identical action + /// would name a different one. Every member's own vote account, under + /// divergent delegation. + Unique, + /// A key the whole batch shares. The index picks *which* shared key, so two + /// shared slots are two accounts, not one. + Shared(usize), + /// The pool's vault, which the settlement has already named as its third + /// account. Free in keys, one byte in indices. + Vault, + /// This spend's own beneficiary, already named. A stake delegation puts the + /// stake account here, and it is the same account the payout goes to. + Beneficiary, +} + +/// A settlement shape: what each spend in the batch brings. +struct Shape { + name: String, + /// Whether the selector makes a call, and so whether each spend also names + /// the callee's program account. Shared across a batch whose members all + /// invoke the same program, which is the interesting case. + calls: bool, + action: Vec, +} + +impl Shape { + fn transfer() -> Self { + Shape { + name: "transfer".into(), + calls: false, + action: Vec::new(), + } + } + + fn cpi(name: &str, action: Vec) -> Self { + Shape { + name: name.into(), + calls: true, + action, + } + } + + /// The stake delegation both experiments below settle for real, in the order + /// `DelegateStake` requires: stake account, vote account, clock, stake + /// history, the unused config account, and the stake authority — which for a + /// member who must never appear on chain can only be the pool's vault. + fn delegation(name: &str, vote: Slot) -> Self { + Shape::cpi( + name, + vec![ + Slot::Beneficiary, + vote, + Slot::Shared(1), + Slot::Shared(2), + Slot::Shared(3), + Slot::Vault, + ], + ) + } + + /// How many keys a spend of this shape adds that no other spend shares: the + /// record, the beneficiary and the relay, plus its unique action accounts. + /// This is the number the ceiling actually turns on. + fn distinct_keys_per_spend(&self) -> usize { + 3 + self + .action + .iter() + .filter(|s| matches!(s, Slot::Unique)) + .count() + } + + fn slots_per_spend(&self) -> usize { + 3 + usize::from(self.calls) + self.action.len() + } +} + +/// A batch of `n` spends of this shape, filled with placeholder keys. +/// +/// Placeholders are legitimate here for the reason `batch_ceiling.rs` sets out: +/// transaction size depends on how many distinct accounts a message names, not on +/// which. What placeholders cannot check is that the shape is the one the program +/// walks — so the real settlements below assert their own wire size against the +/// prediction this makes, and a mistake in the model fails a test instead of +/// quietly biasing the answer. +fn placeholder_batch(wire: &Wire, shape: &Shape, n: usize) -> Vec { + let target = Pubkey::new_unique(); + let shared: Vec = (0..shape.action.len().max(1)) + .map(|_| Pubkey::new_unique()) + .collect(); + (0..n) + .map(|_| { + let beneficiary = Pubkey::new_unique(); + let action = shape + .action + .iter() + .map(|slot| match slot { + Slot::Unique => AccountMeta::new_readonly(Pubkey::new_unique(), false), + Slot::Shared(i) => AccountMeta::new_readonly(shared[*i], false), + Slot::Vault => AccountMeta::new_readonly(wire.vault, false), + Slot::Beneficiary => AccountMeta::new(beneficiary, false), + }) + .collect(); + Spend { + record: Pubkey::new_unique(), + beneficiary, + relay: Pubkey::new_unique(), + target: shape.calls.then_some(target), + action, + } + }) + .collect() +} + +/// The largest batch of this shape that still fits in a packet, and its size, +/// found by serializing rather than by arithmetic. +/// +/// The sweep stops at the first batch that does not fit, which is sound because +/// size is monotonic in the batch length — every extra spend adds slots and can +/// only add keys. It is also necessary: a message can name at most 256 accounts, +/// and a shape with four unique action accounts reaches that ceiling long before +/// the loop would otherwise end, which is a panic rather than a measurement. +fn ceiling_of(svm: &LiteSVM, wire: &Wire, settler: &Keypair, shape: &Shape) -> (usize, usize) { + let mut answer = (0, 0); + for n in 1..=64 { + let batch = placeholder_batch(wire, shape, n); + let bytes = wire_len(&settle_tx(svm, wire, &batch, settler)); + if bytes > PACKET_DATA_SIZE { + break; + } + answer = (n, bytes); + } + answer +} + +// --------------------------------------------------------------------------- +// The pool +// --------------------------------------------------------------------------- + +struct Env { + svm: LiteSVM, + wire: Wire, + payer: Keypair, +} + +fn setup() -> Env { + let mut svm = LiteSVM::new(); + let program_id = Pubkey::new_unique(); + svm.add_program(program_id, &program_bytes()) + .expect("loading the built program"); + + let payer = Keypair::new(); + svm.airdrop(&payer.pubkey(), 1_000_000_000_000).unwrap(); + + Env { + svm, + wire: Wire::new(program_id), + payer, + } +} + +impl Env { + fn send(&mut self, ixs: &[Instruction], signers: &[&Keypair]) -> Result { + let msg = Message::new(ixs, Some(&signers[0].pubkey())); + let tx = Transaction::new(signers, msg, self.svm.latest_blockhash()); + self.svm + .send_transaction(tx) + .map(|m| m.compute_units_consumed) + .map_err(|e| format!("{:?} | logs: {:#?}", e.err, e.meta.logs)) + } + + fn init_pool(&mut self) { + let ix = Instruction::new_with_bytes( + self.wire.program_id, + &MirrorIx::InitPool { + denomination: DENOMINATION, + entry_fee: ENTRY_FEE, + k_floor: K_FLOOR, + } + .pack(), + vec![ + AccountMeta::new(self.payer.pubkey(), true), + AccountMeta::new(self.wire.pool, false), + AccountMeta::new(self.wire.vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let payer = self.payer.insecure_clone(); + self.send(&[ix], &[&payer]).expect("init_pool"); + } + + fn deposit(&mut self, commitment: Field) { + let depositor = Keypair::new(); + self.svm + .airdrop(&depositor.pubkey(), DENOMINATION + 10_000_000) + .unwrap(); + let ix = Instruction::new_with_bytes( + self.wire.program_id, + &MirrorIx::Deposit { + commitment: commitment.to_bytes(), + } + .pack(), + vec![ + AccountMeta::new(depositor.pubkey(), true), + AccountMeta::new(self.wire.pool, false), + AccountMeta::new(self.wire.vault, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + self.send(&[ix], &[&depositor]).expect("deposit"); + } + + fn spend_count(&self) -> u64 { + let mut data = self + .svm + .get_account(&self.wire.pool) + .expect("pool exists") + .data; + Pool::load(&mut data).unwrap().spend_count() + } + + /// A vote account made by the real vote program, so the stake program is + /// handed genuine state rather than bytes this test guessed at. + fn make_vote_account(&mut self) -> Pubkey { + let vote = Keypair::new(); + let node = Keypair::new(); + let lamports = self.svm.minimum_balance_for_rent_exemption(VOTE_STATE_LEN); + let create = solana_system_interface::instruction::create_account( + &self.payer.pubkey(), + &vote.pubkey(), + lamports, + VOTE_STATE_LEN as u64, + &key(VOTE_PROGRAM), + ); + // `VoteInstruction::InitializeAccount(VoteInit)`: a four-byte variant + // index of zero, then the node identity, the authorized voter, the + // authorized withdrawer and a one-byte commission. Hand-encoded because + // the vote interface crate is not a dependency of this program and the + // encoding is four fields long. + let mut data = Vec::with_capacity(101); + data.extend_from_slice(&0u32.to_le_bytes()); + data.extend_from_slice(&node.pubkey().to_bytes()); + data.extend_from_slice(&node.pubkey().to_bytes()); + data.extend_from_slice(&node.pubkey().to_bytes()); + data.push(0); + let init = Instruction::new_with_bytes( + key(VOTE_PROGRAM), + &data, + vec![ + AccountMeta::new(vote.pubkey(), false), + AccountMeta::new_readonly(key(RENT_SYSVAR), false), + AccountMeta::new_readonly(key(CLOCK_SYSVAR), false), + AccountMeta::new_readonly(node.pubkey(), true), + ], + ); + let payer = self.payer.insecure_clone(); + self.send(&[create, init], &[&payer, &vote, &node]) + .expect("initialising a vote account"); + vote.pubkey() + } + + /// A stake account whose authority is the pool's vault, created and + /// initialised by the real stake program. + /// + /// The keypair is supplied by the caller because the stake account is the + /// spend's beneficiary and therefore inside the member's proof: the same + /// address has to exist in every pool a given proof is replayed into. + fn make_stake_account(&mut self, stake: &Keypair) { + let lamports = self.svm.minimum_balance_for_rent_exemption(STAKE_STATE_LEN) + PRE_STAKED; + let create = solana_system_interface::instruction::create_account( + &self.payer.pubkey(), + &stake.pubkey(), + lamports, + STAKE_STATE_LEN as u64, + &key(STAKE_PROGRAM), + ); + // `StakeInstruction::Initialize(Authorized, Lockup)`: variant index zero, + // then staker and withdrawer, then a lockup of no timestamp, no epoch and + // no custodian. Both authorities are the vault, because the member never + // appears on chain and nobody else may move this stake. + let mut data = Vec::with_capacity(116); + data.extend_from_slice(&0u32.to_le_bytes()); + data.extend_from_slice(&self.wire.vault.to_bytes()); + data.extend_from_slice(&self.wire.vault.to_bytes()); + data.extend_from_slice(&0i64.to_le_bytes()); + data.extend_from_slice(&0u64.to_le_bytes()); + data.extend_from_slice(&[0u8; 32]); + let init = Instruction::new_with_bytes( + key(STAKE_PROGRAM), + &data, + vec![ + AccountMeta::new(stake.pubkey(), false), + AccountMeta::new_readonly(key(RENT_SYSVAR), false), + ], + ); + let payer = self.payer.insecure_clone(); + self.send(&[create, init], &[&payer, stake]) + .expect("initialising a stake account"); + } + + /// True once the stake program has written a delegation into the account. + /// + /// `StakeStateV2` is a bincode enum and variant two is `Stake`, so the first + /// four bytes are the whole test. Reading the discriminant rather than + /// trusting the transaction's success means a settlement that returned Ok + /// without the CPI actually landing cannot pass. + fn is_delegated(&self, stake: &Pubkey) -> bool { + self.svm + .get_account(stake) + .map(|a| a.data[..4] == [2, 0, 0, 0]) + .unwrap_or(false) + } +} + +/// A member's proof and the stake account it was bound to. +/// +/// The binding covers the selector, the target program, the beneficiary, the +/// relay fee, the declared account count and the payload — and nothing else. The +/// vote account is a settler-supplied slot, so the *same* proof serves both +/// experiments below: agreeing on a validator and diverging over one are the same +/// spend as far as the member's commitment is concerned, which is precisely why +/// the cost of diverging is measured in keys rather than in proofs. +struct Ticket { + proof: SolanaProof, + stake: Keypair, + relay: Keypair, +} + +fn host_tree(count: usize) -> (MerkleTree, Vec) { + let denom_tag = Field::from_u64(DENOMINATION); + let mut tree = MerkleTree::new().unwrap(); + let mut notes = Vec::new(); + for i in 1..=count as u64 { + let note = Note::new( + Field::from_u64(i * 1_000_003), + Field::from_u64(i * 7_919), + denom_tag, + ); + tree.insert(note.commitment().unwrap()).unwrap(); + notes.push(note); + } + (tree, notes) +} + +fn prove_delegations(keys: &Keys, tree: &MerkleTree, notes: &[Note]) -> Vec { + use ark_std::rand::SeedableRng; + notes + .iter() + .enumerate() + .map(|(index, note)| { + let stake = Keypair::new(); + let merkle_proof = tree.proof(index as u64).unwrap(); + let binding = mirror_core::action_binding( + INVOKE_SIGNED, + &key(STAKE_PROGRAM).to_bytes(), + &stake.pubkey().to_bytes(), + RELAY_FEE, + 6, + &DELEGATE_STAKE, + ); + let witness = Witness { + note: *note, + merkle_proof: &merkle_proof, + root: tree.root().unwrap(), + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([index as u8; 32]); + Ticket { + proof: prove(keys, &witness, &mut rng).expect("proving"), + stake, + relay: Keypair::new(), + } + }) + .collect() +} + +/// One spend of the delegation shape, ready for settlement. +fn delegation_spend(wire: &Wire, ticket: &Ticket, vote: &Pubkey) -> Spend { + let nullifier = ticket.proof.public_inputs[1]; + let (record, _) = spend_address(&wire.program_id, &wire.pool, &nullifier); + Spend { + record, + beneficiary: ticket.stake.pubkey(), + relay: ticket.relay.pubkey(), + target: Some(key(STAKE_PROGRAM)), + action: vec![ + AccountMeta::new(ticket.stake.pubkey(), false), + AccountMeta::new_readonly(*vote, false), + AccountMeta::new_readonly(key(CLOCK_SYSVAR), false), + AccountMeta::new_readonly(key(STAKE_HISTORY_SYSVAR), false), + AccountMeta::new_readonly(key(STAKE_CONFIG), false), + // Not marked a signer here, and it cannot be: a PDA has no key. The + // signature comes from `invoke_signed` inside the program, from seeds + // only the program holds. + AccountMeta::new_readonly(wire.vault, false), + ], + } +} + +/// A fresh pool holding `notes`, with every delegation submitted and pending, and +/// with the stake accounts the proofs named already initialised. +/// +/// `distinct_votes` is the entire difference between the two experiments: with it +/// false the crowd agrees on one validator, with it true every member brings their +/// own. Everything else — the notes, the proofs, the payload, the stake accounts — +/// is identical, which is what makes the two ceilings comparable at all. +fn pending_pool(notes: &[Note], tickets: &[Ticket], distinct_votes: bool) -> (Env, Vec) { + let mut env = setup(); + env.init_pool(); + for note in notes { + env.deposit(note.commitment().unwrap()); + } + + let shared_vote = (!distinct_votes).then(|| env.make_vote_account()); + let mut spends = Vec::new(); + for (i, ticket) in tickets.iter().enumerate() { + env.make_stake_account(&ticket.stake); + env.svm + .airdrop(&ticket.relay.pubkey(), 10_000_000_000) + .unwrap(); + + let nullifier = ticket.proof.public_inputs[1]; + let (record, _) = spend_address(&env.wire.program_id, &env.wire.pool, &nullifier); + let ix = Instruction::new_with_bytes( + env.wire.program_id, + &MirrorIx::SubmitSpend { + proof_a: ticket.proof.proof_a, + proof_b: ticket.proof.proof_b, + proof_c: ticket.proof.proof_c, + root: ticket.proof.public_inputs[0], + nullifier, + selector: INVOKE_SIGNED, + target_program: key(STAKE_PROGRAM).to_bytes(), + beneficiary: ticket.stake.pubkey().to_bytes(), + relay_fee: RELAY_FEE, + action_accounts: 6, + payload: DELEGATE_STAKE.to_vec(), + } + .pack(), + vec![ + AccountMeta::new(ticket.relay.pubkey(), true), + AccountMeta::new(env.wire.pool, false), + AccountMeta::new(record, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + let relay = ticket.relay.insecure_clone(); + env.send(&[ix], &[&relay]) + .unwrap_or_else(|e| panic!("submit {i} failed: {e}")); + + let vote = match shared_vote { + Some(vote) => vote, + None => env.make_vote_account(), + }; + spends.push(delegation_spend(&env.wire, ticket, &vote)); + } + (env, spends) +} + +// --------------------------------------------------------------------------- +// The measurements +// --------------------------------------------------------------------------- + +/// The curve, as a table, before anything is settled. +/// +/// Nothing in *this* test executes: it is the encoding alone, which is free, and +/// that is what makes it worth printing across the whole range rather than at the +/// two points the executed test can afford. Every row says where its number comes +/// from, because "size only" and "settled" are not the same claim and a table +/// that blurs them is the kind of thing this repository does not publish. +/// +/// The transfer row is the control. It has to reproduce the ceiling +/// `batch_ceiling.rs` reached by settling real spends; if it does not, this +/// file's model of the wire is wrong and nothing else it prints can be trusted. +#[test] +fn every_account_an_action_names_costs_the_batch_a_member() { + let svm = LiteSVM::new(); + let wire = Wire::new(Pubkey::new_unique()); + let settler = Keypair::new(); + + let row = |shape: &Shape, taken: &str| { + let (ceiling, bytes) = ceiling_of(&svm, &wire, &settler, shape); + assert!(ceiling > 0, "{} does not fit at all", shape.name); + println!( + " {:<28} {:>5} {:>5} {:>8} {:>6} {taken}", + shape.name, + shape.slots_per_spend(), + shape.distinct_keys_per_spend(), + ceiling, + bytes + ); + (ceiling, bytes) + }; + + println!("\n shape slots keys ceiling bytes taken by"); + let transfer = row(&Shape::transfer(), "size; settled in batch_ceiling.rs"); + let bare_call = row(&Shape::cpi("call, no accounts", Vec::new()), "size only"); + + // The two curves the table exists to separate: the same action account + // *count* costing shared keys, and costing a key per member. Only the second + // curve is a statement about anonymity — the first is what a batch pays for + // making a call at all. + let mut shared_curve = vec![(0usize, bare_call)]; + let mut unique_curve = Vec::new(); + for k in [1usize, 2, 4, 6, 8] { + let shared = row( + &Shape::cpi( + &format!("call, {k} shared"), + (0..k).map(Slot::Shared).collect(), + ), + "size only", + ); + let unique = row( + &Shape::cpi( + &format!("call, {k} per member"), + (0..k).map(|_| Slot::Unique).collect(), + ), + "size only", + ); + assert!( + unique.0 < shared.0, + "with {k} action accounts, a batch whose members name their own \ + accounts carried {} members and a batch that shares them carried \ + {}: naming a distinct account has stopped costing anything, which \ + the encoding cannot do", + unique.0, + shared.0 + ); + shared_curve.push((k, shared)); + unique_curve.push((k, unique)); + } + + let same = row( + &Shape::delegation("delegate, one validator", Slot::Shared(0)), + "size; settled below", + ); + let diverging = row( + &Shape::delegation("delegate, a validator each", Slot::Unique), + "size; settled below", + ); + println!(); + + assert_eq!( + transfer, + (TRANSFER_CEILING, TRANSFER_BYTES), + "the transfer row no longer reproduces the executed measurement in \ + batch_ceiling.rs, so this file's encoding of a settlement is wrong and \ + every other number it prints is unsafe" + ); + assert_eq!( + same, + (SAME_VALIDATOR_CEILING, SAME_VALIDATOR_BYTES), + "the ceiling for a batch delegating to one validator moved" + ); + assert_eq!( + diverging, + (DIFFERENT_VALIDATOR_CEILING, DIFFERENT_VALIDATOR_BYTES), + "the ceiling for a batch delegating to a validator each moved" + ); + + // Both curves have to fall. An action that needs more accounts can never + // carry more members than one that needs fewer, whoever the accounts belong + // to; if that ever stops holding, the model of the wire above is wrong. + for curve in [&shared_curve, &unique_curve] { + for pair in curve.windows(2) { + let [(smaller, (more, _)), (larger, (fewer, _))] = pair else { + unreachable!("windows(2) yields pairs") + }; + assert!( + fewer <= more, + "{larger} action accounts carried {fewer} members and {smaller} \ + carried {more}, so a larger action bought a larger crowd" + ); + } + } +} + +/// The result, settled rather than serialized. +/// +/// Two crowds of members ask the pool to delegate stake on their behalf. In the +/// first every member picked the same validator; in the second every member +/// picked their own. Nothing else differs — not the selector, not the payload, +/// not the proofs, which are literally the same proofs replayed into a second +/// pool, because the vote account is a settlement slot rather than something the +/// member committed to. +/// +/// The agreeing crowd fits one more member. That is the whole finding: an +/// anonymity set is bounded by how much its members' actions have in common, and +/// on Solana the bound is mechanical rather than statistical — a batch that names +/// more distinct accounts does not fit in a packet, whatever the members wanted. +/// +/// Both ceilings are executed against the real `.so` and the real stake program, +/// and both are checked against the byte count the placeholder sweep predicted, +/// so a mistake in the model above fails here rather than passing quietly. The +/// batch one larger than each ceiling is then settled in a fresh pool to show +/// what actually stops it: the SVM takes it, so the packet is the constraint and +/// compute is not close. +#[test] +fn a_crowd_that_agrees_on_its_validator_carries_one_more_member() { + let probe_svm = LiteSVM::new(); + let probe_wire = Wire::new(Pubkey::new_unique()); + let prober = Keypair::new(); + let same_shape = Shape::delegation("same", Slot::Shared(0)); + let diverging_shape = Shape::delegation("diverging", Slot::Unique); + let (same_bracket, _) = ceiling_of(&probe_svm, &probe_wire, &prober, &same_shape); + let (diverging_bracket, _) = ceiling_of(&probe_svm, &probe_wire, &prober, &diverging_shape); + println!( + "the encoding admits {same_bracket} delegations to one validator and \ + {diverging_bracket} to a validator each; proving one more than the larger" + ); + + let keys = generate_reproducible(SEED).expect("setup"); + let (tree, notes) = host_tree(same_bracket.max(diverging_bracket) + 1); + let tickets = prove_delegations(&keys, &tree, ¬es); + + let same = settle_at_ceiling(¬es, &tickets, false, "one validator"); + let diverging = settle_at_ceiling(¬es, &tickets, true, "a validator each"); + + assert_eq!( + (same.0, same.1), + (SAME_VALIDATOR_CEILING, SAME_VALIDATOR_BYTES), + "the executed ceiling for a batch delegating to one validator moved" + ); + assert_eq!( + (diverging.0, diverging.1), + (DIFFERENT_VALIDATOR_CEILING, DIFFERENT_VALIDATOR_BYTES), + "the executed ceiling for a batch delegating to a validator each moved" + ); + assert!( + same.0 > diverging.0, + "agreeing on a validator no longer buys the crowd a member: {} against {}", + same.0, + diverging.0 + ); + println!( + "\n a crowd that agrees on its validator settles {} members; a crowd that \ + diverges settles {}. The divergent batch names {} distinct keys per spend \ + instead of {}, and the extra key is the whole difference.\n", + same.0, + diverging.0, + diverging_shape.distinct_keys_per_spend(), + same_shape.distinct_keys_per_spend() + ); +} + +/// Settles the largest batch that fits, and then demonstrates what stops the next +/// one. Returns the ceiling and its wire size. +/// +/// The shape is derived from `distinct_votes` rather than passed alongside it, so +/// the batch this settles and the batch the placeholder sweep models cannot drift +/// apart. Every real batch is weighed against its placeholder twin at the same +/// length: that is what turns the free sweep from an assumption into a checked +/// prediction, and it is the only thing standing between this file and a table of +/// numbers about a transaction the program could not execute. +/// +/// The sweep runs *downward* from the bracket for the same reason +/// `batch_ceiling.rs` does: a rejected settlement leaves its records pending, so +/// one pool can be asked many times, but an accepted one consumes them, so the +/// first acceptance has to be the answer. +fn settle_at_ceiling( + notes: &[Note], + tickets: &[Ticket], + distinct_votes: bool, + label: &str, +) -> (usize, usize) { + let shape = Shape::delegation( + label, + if distinct_votes { + Slot::Unique + } else { + Slot::Shared(0) + }, + ); + let (mut env, pending) = pending_pool(notes, tickets, distinct_votes); + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + let (bracket, _) = ceiling_of(&env.svm, &env.wire, &settler, &shape); + + let mut ceiling = 0usize; + let mut bytes_at_ceiling = 0usize; + let mut cu_at_ceiling = 0u64; + for n in (1..=bracket).rev() { + let tx = settle_tx(&env.svm, &env.wire, &pending[..n], &settler); + let bytes = wire_len(&tx); + let predicted = wire_len(&settle_tx( + &env.svm, + &env.wire, + &placeholder_batch(&env.wire, &shape, n), + &settler, + )); + assert_eq!( + bytes, predicted, + "the placeholder model of {label} weighs a batch of {n} differently \ + from the real one, so the table this file prints describes a \ + settlement that does not exist" + ); + assert!( + bytes <= PACKET_DATA_SIZE, + "the placeholder bracket disagreed with a real batch of {n}: {bytes} bytes" + ); + match env.svm.send_transaction(tx) { + Ok(meta) => { + ceiling = n; + bytes_at_ceiling = bytes; + cu_at_ceiling = meta.compute_units_consumed; + break; + } + Err(e) => println!( + "{n} delegations fit in {bytes} bytes but failed: {:?}", + e.err + ), + } + } + assert!(ceiling > 0, "no batch of delegations settled at all"); + assert_eq!( + env.spend_count(), + ceiling as u64, + "the settlement that succeeded did not settle the whole batch" + ); + // The transaction succeeding is not evidence that the delegations happened; + // the stake accounts saying so is. + for spend in &pending[..ceiling] { + assert!( + env.is_delegated(&spend.beneficiary), + "settlement returned Ok but {} was never delegated", + spend.beneficiary + ); + } + println!( + "settled {ceiling} real stake delegations ({label}) in one transaction: \ + {bytes_at_ceiling} bytes ({} to spare), {cu_at_ceiling} CU of \ + {DEFAULT_COMPUTE_BUDGET}", + PACKET_DATA_SIZE - bytes_at_ceiling + ); + assert!( + cu_at_ceiling < DEFAULT_COMPUTE_BUDGET, + "a full settlement used {cu_at_ceiling} CU, over the default budget" + ); + + // Why one more fails, and it takes two judges. litesvm never sees a packet, + // so it will happily execute a batch no validator would accept; the wire + // format never sees a compute meter. The same batch of `ceiling + 1` is + // measured on the wire and replayed into a second, identical pool where the + // SVM rules on it. + let over = ceiling + 1; + let (mut second, again) = pending_pool(notes, tickets, distinct_votes); + let other = Keypair::new(); + second.svm.airdrop(&other.pubkey(), 10_000_000_000).unwrap(); + let tx = settle_tx(&second.svm, &second.wire, &again[..over], &other); + let bytes_over = wire_len(&tx); + assert_eq!( + bytes_over, + wire_len(&settle_tx( + &second.svm, + &second.wire, + &placeholder_batch(&second.wire, &shape, over), + &other, + )), + "the placeholder model and the real batch disagree at {over} spends" + ); + let verdict = second.svm.send_transaction(tx); + + assert!( + bytes_over > PACKET_DATA_SIZE, + "{over} delegations fit in {bytes_over} bytes, so the packet is not what \ + stops them and the sweep above stopped one short" + ); + let Ok(over_meta) = &verdict else { + panic!( + "the SVM refused {over} delegations, so the packet limit is not the \ + only thing in the way and this ceiling reports the wrong constraint" + ) + }; + assert!( + over_meta.compute_units_consumed < DEFAULT_COMPUTE_BUDGET, + "settling {over} delegations cost {} CU, at or over the default budget, so \ + compute now binds alongside the packet", + over_meta.compute_units_consumed + ); + println!( + "{over} delegations ({label}): rejected by the wire at {bytes_over} bytes, \ + {} over the {PACKET_DATA_SIZE}-byte limit — while the SVM settled the same \ + batch in {} CU, so compute is not the constraint", + bytes_over - PACKET_DATA_SIZE, + over_meta.compute_units_consumed + ); + + (ceiling, bytes_at_ceiling) +} From 4ccbf4e2b870751db93a829f1f32c38d5a00933c Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 00:02:51 -0300 Subject: [PATCH 65/83] fix: numbers that do not reproduce, and claims wider than their evidence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An audit of everything the repository publishes, against the code and against itself. The worst of it was compute. Every CU figure in the docs was a print from a test run, never asserted, and settlement's cost moves by thousands between runs because each run derives its record addresses from freshly generated nullifiers and find_program_address searches a different number of bumps. Three runs here gave 40251, 41751 and 46251 for the same batch; the docs said 39,820. GROTH16_INTEGRATION.md even named the command a reader should run to reproduce its number, which is the one command that falsifies it. So the tests now assert bounds and the docs quote bounds. A figure that would fail on nothing was worse than no figure. Three claims were wider than what supports them: The selection check published two rows as budget margins. Only one is. The staking pair used identical parameters — depth 16, page cap 24 — and differs by the tracer fix, so its "expensive to trace" group is members the broken tracer failed on, not members a smaller budget could not reach. Still a real check, a different one, and now labelled as such. "A third of the cost per member" was two-thirds: 1,776 calls for 54 resolved against 1,887 for 38. The measurement log's closing section was four runs behind — it quoted the broken-tracer headline, said only one pool had been measured, and counted four runs where there are eight. The rest is smaller: a paragraph duplicated verbatim, six samples that are seven, command lists missing every member-facing command, an artifact layout and two commands the method specified and nobody built, and a quick-start block whose five commands were all missing required flags — printed directly under the new install section, so a reader would paste it and get five clap errors. Also documents TREE_DEPTH and ROOT_HISTORY, which appeared in no document, and adds the prerequisites: make verify needs the Agave toolchain for cargo-build-sbf, which was stated nowhere outside CI. --- .gitignore | 1 + README.md | 121 ++++++++++++++----- crates/mirror-cli/src/crowd.rs | 13 ++- data/program-id-devnet.txt | 1 - data/soak-notes-20000011.json | 1 - data/soak-notes-20000013.json | 1 - data/soak-notes-20000017.json | 1 - data/soak-notes-20000019.json | 1 - data/soak-notes.json | 1 - docs/ARCHITECTURE.md | 21 ++-- docs/CROWD.md | 8 +- docs/GROTH16_INTEGRATION.md | 12 +- docs/MEASUREMENT_LOG.md | 41 ++++--- docs/PLAN.md | 9 +- docs/PROVENANCE_METHOD.md | 143 ++++++++++++----------- docs/THREAT_MODEL.md | 4 +- programs/mirror-pool/tests/end_to_end.rs | 25 +++- 17 files changed, 271 insertions(+), 133 deletions(-) delete mode 100644 data/program-id-devnet.txt delete mode 100644 data/soak-notes-20000011.json delete mode 100644 data/soak-notes-20000013.json delete mode 100644 data/soak-notes-20000017.json delete mode 100644 data/soak-notes-20000019.json delete mode 100644 data/soak-notes.json diff --git a/.gitignore b/.gitignore index a926fa12..e10df076 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ target/ !programs/**/fixtures/*.so .env keys/ +.claude/ *.keypair.json notes/ sp4/ diff --git a/README.md b/README.md index 2a9a2143..cbf1f32b 100644 --- a/README.md +++ b/README.md @@ -20,9 +20,46 @@ delegation or a governance vote needs and what a transfer cannot do. Rust end to end. MIT. No Anchor, no Circom, no JavaScript anywhere in the proving path. +## Getting it running + +Two prerequisites, and only two. + +**Rust.** `rust-toolchain.toml` pins 1.97.1 with `rustfmt` and `clippy`, so +[rustup](https://rustup.rs) installs the right version by itself the first time +you build — no manual step. + +**The Agave (Solana) toolchain**, for `cargo-build-sbf`. The on-chain program is +compiled to SBF and the test suite loads that `.so` into a real SVM, so without +this the suite cannot run at all: + +``` +sh -c "$(curl -sSfL https://release.anza.xyz/stable/install)" +solana --version # confirms it is on PATH +``` + +Then: + +``` +git clone https://github.com/solanabr/mirror-pool && cd mirror-pool +make verify # fmt, clippy -D warnings, build-sbf, 216 tests ``` -make verify # fmt, clippy -D warnings, tests, build-sbf + +`make verify` is the whole check, and it is the same command CI runs — see +[`.github/workflows/ci.yml`](.github/workflows/ci.yml), which invokes `make +verify` and nothing else, so the two cannot drift apart. On a cold clone it +takes a few minutes, most of it compiling arkworks. Nothing here needs a +network, a validator or a funded wallet. + +To get the member-facing tool as a binary: + ``` +cargo build --release # target/release/mirror +``` + +[`docs/USAGE.md`](docs/USAGE.md) walks the whole journey from there — creating a +note through to settling a batch — with real devnet output for every command. +[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) is the design and why each +decision is what it is. ## The problem this takes as its subject @@ -37,7 +74,7 @@ No pool controls where its users' money came from. What that channel can be is *measured*, and measured honestly — which as far as we can tell nobody has done against live Solana data with a published method. -So this submission claims exactly two things: +So this project claims exactly two things: 1. **The action side is closed.** Actions execute from the pool's vault PDA, so an action's on-chain funding trace leads to the pool and is identical for every @@ -65,8 +102,9 @@ So this submission claims exactly two things: rather than in the observed ones. There is no budget at which this distribution becomes well-observed. - `docs/MEASUREMENT_LOG.md` has all eight runs, including the three that - produced no headline and the one whose pre-registered prediction was wrong. + `docs/MEASUREMENT_LOG.md` has all eight runs, including the three the tool + itself refused to publish a headline from and the one whose pre-registered + prediction turned out wrong. Anything we cannot support with a measurement whose method is published, we do not say. There is a section below of things we deliberately do not claim. @@ -75,7 +113,7 @@ not say. There is a section below of things we deliberately do not claim. | | | |---|---| -| `programs/mirror-pool` | The on-chain program. `submit_spend`, proof and all, measured at **101,127 CU** on a real SVM. | +| `programs/mirror-pool` | The on-chain program. `submit_spend`, proof and all, costs about **101,000 CU** on a real SVM — half the default budget for one instruction. | | `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain. | | `crates/mirror-circuit` | R1CS gadget, membership circuit, prover, key export. | | `crates/mirror-provenance` | The funding-provenance measurement. | @@ -89,15 +127,22 @@ and what the chain does cannot pass unnoticed. ## Using it ``` -mirror note-new --denomination D --out m1.json # a note is a local secret -mirror deposit --note m1.json # escrow it, join the set -mirror tree # rebuild the accumulator from chain -mirror spend --note m1.json --to --relay relay.json -mirror settle # permissionless +export P= # every command needs it; D is the pool's denomination + +mirror note-new --denomination D --out m1.json # a note is a local secret +mirror deposit --program $P --note m1.json # escrow it, join the set +mirror tree --program $P --denomination D # rebuild the accumulator +mirror spend --program $P --note m1.json \ + --to --relay relay.json # the relay signs, never you +mirror settle --program $P --denomination D # permissionless ``` -`docs/USAGE.md` is the walkthrough, and every line of output in it was produced -by running the command against devnet. +`docs/USAGE.md` is the walkthrough for these six, and every line of output in it +was produced by running the command against devnet. The other ten subcommands +are for operating a pool (`setup`, `verify-setup`, `soak`, `crowd`) and for the +measurement (`check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, +`selection`); `--help` documents each, and `docs/MEASUREMENT_LOG.md` gives the +exact invocation for every published number. **No server, no indexer, no account with anybody.** The program stores only the accumulator's frontier — enough to append a leaf, not enough to prove one is @@ -178,9 +223,15 @@ The brief asks for an anonymity set for *behaviour*, not for funds. That distinction is load-bearing here, so it is tested rather than asserted: ``` -settled 4 real CPI actions in one transaction, 39,820 CU +settled 4 real CPI actions in one transaction, 40251 CU ``` +That figure moves by a few thousand between runs — the accounts are generated +fresh each time and `find_program_address` searches a different number of bumps +to derive each record's address — so what the test *asserts* is the property +rather than the number: four CPI actions and their payouts stay under 60,000 CU, +comfortably inside one instruction's default budget. + `a_crowd_of_members_perform_a_real_protocol_action_together` seeds a pool, loads the **real SPL Memo program** into the SVM, and has four members each prove membership and request the identical memo. Settlement invokes Memo four @@ -203,8 +254,8 @@ bolted on: the callee as a *signer*, which is what a stake delegation or a governance vote needs and what a transfer does not: somebody must sign as the authority, and for a member who must never appear on chain, that somebody can only be the - pool. `the_pool_signs_an_action_as_its_own_authority` proves it at **30,827 CU** - against real SPL Memo — a program that refuses any account handed to it that + pool. `the_pool_signs_an_action_as_its_own_authority` proves it under + **50,000 CU** against real SPL Memo — a program that refuses any account handed to it that has not signed, and that names its signers in its logs. The test reads that log for the vault's own key, so the claim rests on someone else's program. Devnet carries the case this exists for: a **real stake delegation**, with the pool as @@ -309,7 +360,7 @@ mirror selection --earlier lo.json --later hi.json # are the unresolved missing ``` `data/sample-privacycash-run6.json` is the committed artifact behind the -headline. Six samples are committed in all, and they are what make each +headline. Seven samples are committed in all, and they are what make each correction checkable rather than merely described: | file | run | what it is | @@ -401,14 +452,25 @@ and it is normally asserted and left alone. It is testable: collect one frame at two budgets, split the resolved members into *cheap to trace* and *expensive to trace*, and ask whether the two groups have the same class distribution. -| population | cheap | expensive | difference, 95% | -|---|---|---|---| -| staking control | 16, ρ 0.0743 | 22, ρ 0.0585 | −0.0144 … +0.0786 | -| privacy pool | 39, ρ 0.1179 | 15, ρ 0.1250 | −0.1507 … +0.0704 | +| population | pair | cheap | expensive | difference, 95% | +|---|---|---|---|---| +| privacy pool | depth 8/cap 8 → 16/24 | 39, ρ 0.1179 | 15, ρ 0.1250 | −0.1507 … +0.0704 | +| staking control | same budget, tracer fixed | 16, ρ 0.0743 | 22, ρ 0.0585 | −0.0144 … +0.0786 | Neither separates: at this margin, being resolvable does not pick out particular -provenance classes. Evidence, not proof — it speaks for the members just beyond -a cheaper budget, not for those beyond the larger one. +provenance classes. + +**Only the first row is a budget margin**, and the second is weaker than it +looks. The two staking runs used identical parameters — depth 16, page cap 24 — +and differ by the tracer fix rather than by budget, so its *expensive* group is +"members the broken tracer failed on" rather than "members a smaller budget +could not reach". It is a real check on whether that bug selected for particular +classes, which is worth knowing, and it is not a second budget margin. The +manifests in `data/` carry the parameters, so this is checkable rather than +taken on trust. + +Evidence, not proof, in either case — the first row speaks for the members just +beyond a cheaper budget, not for those beyond the larger one. Had it separated, that would have been the more important result, and it would have invalidated the cross-population comparison outright rather than merely @@ -448,7 +510,8 @@ additional resolved member, against 50 for the population as a whole.** The control is not under-resolved because we were stingy. Its remaining members have genuinely long funding chains, and the *staking* pool — where nobody wants deniability — turns out to be markedly harder to trace than the privacy pool, -which resolved at 65% for a third of the cost per member. We do not claim to know +which resolved at 65% for two-thirds of the cost per member (33 RPC calls +per resolved member against 50). We do not claim to know why, and the explanation that would flatter us is one of the two candidates, which is exactly why we are not asserting it. @@ -537,7 +600,7 @@ multi-party ceremony, not more SOL. the public seed and compares it element by element against the one compiled into the program — expected digest `b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7`. Reproducible - and insecure is a coherent position for an unaudited submission; the incoherent + and insecure is a coherent position for an unaudited protocol; the incoherent one is a setup that is *both* insecure and unreproducible, which is what publishing the entropy while withholding the proving key produces — nobody can verify the key, and nobody can regenerate it either. @@ -549,9 +612,11 @@ multi-party ceremony, not more SOL. populations is demonstrated as machinery and unproven as a finding. - Not audited. -`docs/MEASUREMENT_LOG.md` records every collection run, including the three that -produced no headline and the two the tool itself refused. A measurement project -that keeps only its successful runs is selecting rather than reporting. +`docs/MEASUREMENT_LOG.md` records every collection run, including the three the +tool itself refused to publish a headline from — twice on the failure gate, once +on the bracket — and the one whose pre-registered prediction turned out wrong. A +measurement project that keeps only its successful runs is selecting rather than +reporting. ## Documentation @@ -564,5 +629,5 @@ that keeps only its successful runs is selecting rather than reporting. | `docs/THREAT_MODEL.md` | The adversary, what holds, and every place it stops. | | `docs/PROOF.md` | Devnet signatures for every flow, and the rejections. | | `docs/CROWD.md` | Six members delegating to six different validators in one devnet transaction, and what divergence costs. | -| `docs/USAGE.md` | Every command, with real output. | +| `docs/USAGE.md` | The six member-facing commands, end to end, with real devnet output. | | `docs/PLAN.md` | The design as decided, and where the shipped protocol departs from it. | diff --git a/crates/mirror-cli/src/crowd.rs b/crates/mirror-cli/src/crowd.rs index 09dfa889..a6a2c5c0 100644 --- a/crates/mirror-cli/src/crowd.rs +++ b/crates/mirror-cli/src/crowd.rs @@ -1021,7 +1021,7 @@ fn report(outcome: &Outcome) -> String { conversation. A delegation costs roughly an order of magnitude more per member \ than a payment does.\n\n\ Each member's Groth16 proof was verified earlier, in their own `submit_spend`, \ - which is measured at 101,127 CU. That is why the two phases exist: verifying \ + which costs about 101,000 CU. That is why the two phases exist: verifying \ {members} proofs here would cost over 600,000 CU — comfortably past the 200,000 a \ single instruction gets by default, and a large fraction of the 1.4M a whole \ transaction may ever request.\n\n", @@ -1055,6 +1055,17 @@ fn report(outcome: &Outcome) -> String { } md.push('\n'); + md.push_str("## Reproducing this document\n\n"); + md.push_str(&format!( + "Every value above was written by the run that produced it, and the run recorded \ + them in `data/crowd-result-{denomination}.json`. `mirror crowd --render-only` \ + rebuilds this file from that record without touching a cluster, so the prose \ + around a number can be improved without re-running a measurement — and a number \ + cannot be changed without re-running one.\n\n\ + Every address and signature here is on devnet and can be checked against the \ + cluster rather than against this file.\n\n" + )); + md.push_str("## Scope\n\n"); md.push_str( "Devnet, and one operator. This is a functional and quantitative result, not an \ diff --git a/data/program-id-devnet.txt b/data/program-id-devnet.txt deleted file mode 100644 index 2c737be0..00000000 --- a/data/program-id-devnet.txt +++ /dev/null @@ -1 +0,0 @@ -8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa diff --git a/data/soak-notes-20000011.json b/data/soak-notes-20000011.json deleted file mode 100644 index dfb5abe0..00000000 --- a/data/soak-notes-20000011.json +++ /dev/null @@ -1 +0,0 @@ -[[1785102932292732,14136187712264],[1785102932292739,14136187712277],[1785102932292746,14136187712290],[1785102932292753,14136187712303],[1785103404294148,14136191450032],[1785103404294155,14136191450045],[1785103404294162,14136191450058],[1785103404294169,14136191450071]] \ No newline at end of file diff --git a/data/soak-notes-20000013.json b/data/soak-notes-20000013.json deleted file mode 100644 index 8950153d..00000000 --- a/data/soak-notes-20000013.json +++ /dev/null @@ -1 +0,0 @@ -[[1785103497294427,14136192186499],[1785103497294434,14136192186512],[1785103497294441,14136192186525],[1785103497294448,14136192186538],[1785103518294490,14136192352798]] \ No newline at end of file diff --git a/data/soak-notes-20000017.json b/data/soak-notes-20000017.json deleted file mode 100644 index d8866ebd..00000000 --- a/data/soak-notes-20000017.json +++ /dev/null @@ -1 +0,0 @@ -[[1785103732295132,14136194047464],[1785103732295139,14136194047477],[1785103732295146,14136194047490],[1785103732295153,14136194047503],[1785103754295198,14136194221682]] \ No newline at end of file diff --git a/data/soak-notes-20000019.json b/data/soak-notes-20000019.json deleted file mode 100644 index ea0a659c..00000000 --- a/data/soak-notes-20000019.json +++ /dev/null @@ -1 +0,0 @@ -[[1785105260299716,14136206147696],[1785105260299723,14136206147709],[1785105260299730,14136206147722],[1785105260299737,14136206147735],[1785105260299744,14136206147748],[1785105287299797,14136206361509]] \ No newline at end of file diff --git a/data/soak-notes.json b/data/soak-notes.json deleted file mode 100644 index d685c825..00000000 --- a/data/soak-notes.json +++ /dev/null @@ -1 +0,0 @@ -[[1785042846112474,14135711891230],[1785042846112481,14135711891243],[1785042846112488,14135711891256],[1785042846112495,14135711891269],[1785042867112537,14135712057529]] \ No newline at end of file diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index e8b235bd..05d9101f 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -14,8 +14,10 @@ mirror-core field, Poseidon, Merkle accumulator, notes (linked on-chain mirror-circuit R1CS gadget, membership circuit, prover (host only) mirror-pool the on-chain program mirror-provenance funding-provenance measurement (host only) -mirror-cli setup, verify-setup, check-endpoint, seeds, collect, - analyze, compare, selection, soak +mirror-cli members: init-pool, note-new, deposit, tree, spend, settle + operators: setup, verify-setup, soak, crowd + measuring: check-endpoint, seeds, collect, analyze, + compare, selection ``` `mirror-core` is shared by the program and the host deliberately: a commitment, @@ -92,11 +94,11 @@ with root `R`, my nullifier is `H1(k)`, and this proof is bound to `action`.* Three public inputs, and that is a cost decision. On-chain verification measures as `74,179 + 5,661 × N` compute units, so each input costs about 5.7k CU. See `GROTH16_INTEGRATION.md`; the figure this repository reproduces directly is the -whole `submit_spend` instruction at 101,127 CU. +whole `submit_spend` instruction at about 101,000 CU. | public input | why it cannot be a witness | |---|---| -| `root` | the program checks it against its own root history | +| `root` | the program checks it against its own root history — the last `ROOT_HISTORY = 128` roots, so a proof stays valid for 128 deposits after the one it was built against, and no longer | | `nullifier` | the program records it to prevent replay | | `action_binding` | the program recomputes it from the action it executes | @@ -108,7 +110,7 @@ participates in verification only through the R1CS columns that reference it; an input used in no constraint has an all-zero column, its `gamma_abc` term is the identity, and *any* value satisfies the equation. Without that one constraint a relay could swap the action after proving and the proof would still verify. -`tests/onchain_layout.rs` tampers with that exact input and asserts the real +`crates/mirror-circuit/tests/onchain_layout.rs` tampers with that exact input and asserts the real verifier rejects it, so the property is checked rather than reasoned about. ### Three-way parity @@ -139,7 +141,12 @@ is worse for every member in it. There is no privileged authority, so no key whose loss freezes the escrow. **`deposit`** — escrows exactly the pool's denomination, read from the pool and -never from the instruction, and appends the commitment to the accumulator. +never from the instruction, and appends the commitment to the accumulator. The +tree is `TREE_DEPTH = 20`, so a pool holds up to 1,048,576 notes; the program +keeps only the frontier — one node per level — which is enough to append a leaf +and produce a root, and not enough to prove any particular leaf is in the tree. +Recovering the leaves is the client's job, and `mirror tree` does it from the +transaction history. **`submit_spend`** — verifies the Groth16 proof on-chain, burns the nullifier, records the authorised action. Pays out nothing. @@ -280,7 +287,7 @@ is how a provenance figure comes to mean less than it appears to. |---|---|---| | unresolved bracket | what if the unresolved had landed differently? | exact bound over both extremes | | sampling spread | how much of `ρ` is *which* members we drew? | bootstrap over members, seeded and published | -| selection | are the resolved members a fair draw of the classes? | one frame at two budgets, cheap vs expensive to trace | +| selection | are the resolved members a fair draw of the classes? | one frame at two budgets, cheap vs expensive to trace (see `README.md` for which published pair is a genuine budget margin) | The third is the one usually left as an assumption. `mirror selection` tests it, and **separation is the bad outcome**: it would mean the unresolved are not diff --git a/docs/CROWD.md b/docs/CROWD.md index facd5385..d85e7f26 100644 --- a/docs/CROWD.md +++ b/docs/CROWD.md @@ -47,7 +47,7 @@ At 6 members the settlement weighs 1194 bytes with 38 to spare; one more member This settlement burned **142856 CU of the 200000** a single instruction gets by default — 71% of the budget, for 6 payouts and 6 cross-program invocations. That is a different regime from a batch of plain transfers: `ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_the_eleventh` settles ten of those in 19,545 CU, where compute is nowhere in the conversation. A delegation costs roughly an order of magnitude more per member than a payment does. -Each member's Groth16 proof was verified earlier, in their own `submit_spend`, which is measured at 101,127 CU. That is why the two phases exist: verifying 6 proofs here would cost over 600,000 CU — comfortably past the 200,000 a single instruction gets by default, and a large fraction of the 1.4M a whole transaction may ever request. +Each member's Groth16 proof was verified earlier, in their own `submit_spend`, which costs about 101,000 CU. That is why the two phases exist: verifying 6 proofs here would cost over 600,000 CU — comfortably past the 200,000 a single instruction gets by default, and a large fraction of the 1.4M a whole transaction may ever request. The packet still binds first — 6 members is where the bytes run out, and the budget is not exhausted there — but the two limits are no longer independent, and that is the part worth stating plainly. The usual answer to a settlement that runs out of compute is to ask for more with a `SetComputeUnitLimit` instruction. Measured against this very batch, that instruction costs **40 bytes**, and a full settlement has 38 to spare. **Raising the budget means dropping a member.** @@ -78,6 +78,12 @@ So the ceiling here is not a byte count that a future compute optimisation would | submit_spend | [`4haLJna7fmp85ahKppvXR8bVgGSFmhsftxy1XEfuztZ8mKVKxdVzdwLVwS6CedD2fmzdMwPWc5Na2DAaNvUZdgQM`](https://explorer.solana.com/tx/4haLJna7fmp85ahKppvXR8bVgGSFmhsftxy1XEfuztZ8mKVKxdVzdwLVwS6CedD2fmzdMwPWc5Na2DAaNvUZdgQM?cluster=devnet) | note 5, relay-signed, delegate to 2u83Dx5qPV4QnujjJQv8v2SoqG1ixuAxPK5Jwhtkovd1 | | settle_epoch | [`5SqtqpuJw3pAoBMMB9DmXaqFr93rWsnqWMyacRZ3ZpP5eQpacLTAjrYugAW2ni3PMucAzDLJ4TsLZU3aQd3ZVMPZ`](https://explorer.solana.com/tx/5SqtqpuJw3pAoBMMB9DmXaqFr93rWsnqWMyacRZ3ZpP5eQpacLTAjrYugAW2ni3PMucAzDLJ4TsLZU3aQd3ZVMPZ?cluster=devnet) | 6 delegations to 6 different validators, one transaction, 1194 bytes | +## Reproducing this document + +Every value above was written by the run that produced it, and the run recorded them in `data/crowd-result-43000007.json`. `mirror crowd --render-only` rebuilds this file from that record without touching a cluster, so the prose around a number can be improved without re-running a measurement — and a number cannot be changed without re-running one. + +Every address and signature here is on devnet and can be checked against the cluster rather than against this file. + ## Scope Devnet, and one operator. This is a functional and quantitative result, not an anonymity claim about a live crowd: the relays here were funded from the same wallet that made the deposits, which is exactly the linkage `USAGE.md` tells a real member to avoid. What the run establishes is that a batch of *divergent* actions settles as one, that every member reached the validator they chose, and what such a batch costs in packet space. diff --git a/docs/GROTH16_INTEGRATION.md b/docs/GROTH16_INTEGRATION.md index 56358ce5..ec382cf3 100644 --- a/docs/GROTH16_INTEGRATION.md +++ b/docs/GROTH16_INTEGRATION.md @@ -58,8 +58,16 @@ than a slice. Measured in LiteSVM against a real SBF build, with a scratch circuit per input count that is not part of this repository. The figure this repository does -reproduce is the whole `submit_spend` instruction at three inputs — 101,127 CU, -printed by `cargo test -p mirror-pool-program --test end_to_end -- --nocapture`. +reproduce is the whole `submit_spend` instruction at three inputs — about +101,000 CU, printed by `cargo test -p mirror-pool-program --test end_to_end -- +--nocapture`. + +The instruction's cost is not identical run to run. Each run derives the spend +record's address from a freshly generated nullifier, and `find_program_address` +searches a different number of bumps to get there, which moves the total by a +few thousand CU. So the test asserts a bound — under 110,000 CU, against the +200,000 a single instruction gets by default — rather than pinning a figure that +would fail on nothing. | Public inputs | `verify()` | |---|---| diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index 57c4241a..7dfc725e 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -19,10 +19,16 @@ project that only records its successful runs is not reporting, it is selecting. ``` mirror analyze --sample data/sample-privacycash-run6.json mirror compare --sample data/sample-privacycash-run6.json --against data/sample-marinade-run8.json +mirror selection --earlier data/sample-privacycash.json --later data/sample-privacycash-run6.json mirror selection --earlier data/sample-marinade.json --later data/sample-marinade-run7.json ``` -All three are pure and offline. Nothing below needs our endpoint, our key, or +The two `selection` runs are the two rows of the table in `README.md`, and only +the first is a budget margin — the marinade pair used identical parameters and +differs by the tracer fix. The manifests carry the parameters, so which is which +is checkable here rather than asserted. + +All of them are pure and offline. Nothing below needs our endpoint, our key, or our word for how a run behaved. --- @@ -168,7 +174,7 @@ the failure gate and once on the bracket, and each refusal was correct. 44 unresolved are budget outcomes, chains that ran out of depth or paging before reaching a class. That is a bigger call budget spent on depth rather than on sample size, which is a straightforward thing to buy and not something this -submission claims to have bought. +run claims to have bought. ## Run 4 — pre-registered before collection, 2026-07-25 @@ -592,7 +598,8 @@ margin above it, several times that. **A depositor population that resists provenance tracing this hard is itself a result**, and it is the opposite of what one would guess: the *staking* pool — where nobody is seeking deniability — is markedly harder to trace than the -privacy pool, whose depositors resolved at 65% for a third of the cost per member. +privacy pool, whose depositors resolved at 65% for two-thirds of the cost per +member — 1,776 calls for 54 resolved against 1,887 for 38. We do not claim to know why. Two candidates we cannot separate with this data: staking attracts older wallets with deeper histories, or privacy-pool depositors @@ -603,26 +610,30 @@ is precisely why we are not asserting it. ## What we do not conclude **Nothing about how private that pool is, and the headline does not become that -claim by clearing a gate.** ρ = 0.1032 is a point estimate inside a bracket -spanning 0.032 to 0.132, drawn from one frame of one pool at one moment, and its -class distribution is demonstrably heavy-tailed and two-thirds unobserved. It is -published because the method is published, not because it settles anything. +claim by clearing a gate.** ρ = 0.0955 is a point estimate inside an unresolved +bracket spanning 0.0350 to 0.1136, drawn from one frame of one pool at one +moment, and its class distribution is demonstrably heavy-tailed and mostly +unobserved. It is published because the method is published, not because it +settles anything. Three things it specifically does not support: -- **A comparison between pools.** ρ is designed to be comparable across pools — - that is why it is the headline — but only one pool has been measured. The - property is asserted here and not yet exercised. +- **A ranking of the two pools measured.** ρ is designed to be comparable across + pools, and the machinery was exercised: Runs 5, 7 and 8 collected a staking + control and ran `compare` against it. The tool **refused** to report the + difference, because the control resolved fewer than half its members and the + gate that catches exactly that failure fired. Comparability is demonstrated as + machinery and unachieved as a result. - **An extrapolation to larger `k`.** Effective-k measured at small `k` - understates the steady-state loss, and the heavy tail found in Run 4 is a - reason to expect that gap to widen rather than close. + understates the steady-state loss, and the heavy tail is a reason to expect + that gap to widen rather than close. - **A statement about any individual member.** The metric is a property of a distribution. Nothing here identifies anyone, and the worst-case class of size one is a feature of heavy-tailed provenance in general, not a finding about a person. -What the four runs do support is narrower and, we think, more useful: that this +What the eight runs do support is narrower and, we think, more useful: that this channel is measurable from live Solana data with a published method, that the measurement is expensive and the tail does not close, and that a pipeline built -to refuse is one that refuses — three times, correctly, before it produced a -number. +to refuse is one that refuses — before it produced a number, and again after, +including on a comparison this document would have preferred to report. diff --git a/docs/PLAN.md b/docs/PLAN.md index d215f3e4..5d54449b 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -1,4 +1,9 @@ -# mirror-pool — implementation plan +# The design as decided, and where the protocol departs from it + +This is the design record: what was decided before the protocol was built, and — +in the last section — the six things that did not ship and why each was a +decision rather than an overrun. It is kept because the departures are the +interesting part; `ARCHITECTURE.md` describes what exists today. ## The thesis @@ -101,7 +106,7 @@ and the relay fee, so every economically meaningful field is inside the proof. ### Two-phase epochs — synchronization without a CU explosion -The bounty's premise is synchronized crowds: many identical actions landing +The premise is synchronized crowds: many identical actions landing together so that timing and ordering carry no signal. Verifying `N` proofs in one transaction does not fit — three proofs exhaust the transaction size limit long before the compute limit. diff --git a/docs/PROVENANCE_METHOD.md b/docs/PROVENANCE_METHOD.md index f141972e..fc240824 100644 --- a/docs/PROVENANCE_METHOD.md +++ b/docs/PROVENANCE_METHOD.md @@ -1,6 +1,19 @@ # Funding-Provenance Effective-k — measurement specification -Status: specification, v1. Implement against this document. +Status: specification, v1 — and `crates/mirror-provenance` was built against it. + +Read it as the method rather than as a description of the code. Most of it +shipped; three things did not, and each is marked where it appears rather than +quietly dropped: + +- **`verify-labels`** (§5.4) was specified to re-check every curated anchor + against the chain. The anchor set exists in `classify.rs`; the command that + falsifies it does not, so Tier-2 labels rest on their cited sources alone. +- **The multi-file artifact layout** (§7.3) became one JSON document per run. + What shipped is described there. +- **The `n = 1,100` baseline frame** (§3) was not collected. The published runs + are the smaller ones in `docs/MEASUREMENT_LOG.md`, and what that costs the + estimate is stated there rather than here. Every empirical number below names its source. Numbers tagged **[M 2026-07-25]** were measured directly against Solana mainnet on that date (probe method stated inline); @@ -79,7 +92,7 @@ over the survivors. Prior over `X` is uniform on `A`. - compromise of any party. Those are separate channels. Folding any of them in inflates the number and destroys -comparability with the published Ethereum results we benchmark against (§10.3). +comparability with the published Ethereum results we benchmark against (§10.2). ### 1.2 The two-sidedness rule @@ -659,14 +672,6 @@ from `no-incoming-edge` for a reason that is the whole discipline of §6 in mini > is indistinguishable from a genuinely unresolvable wallet. The bias falls hardest on > active wallets, which are the ones a provenance study most needs to resolve. -> **Failure mode.** Scanning an address's most recent transactions to find its funder. -> `getSignaturesForAddress` returns newest-first, so taking the first handful is the -> path of least resistance — and funding is by definition among an address's *oldest* -> transactions. For any address with more history than the scan window, this reads the -> wrong end of the record, and it fails **silently**: the output is "unresolved", which -> is indistinguishable from a genuinely unresolvable wallet. The bias falls hardest on -> active wallets, which are the ones a provenance study most needs to resolve. - **Independent corroboration of the rule.** Dune Spellbook contains `addresses_events_solana.first_funded_by`, described in its own `schema.yml` as *"Table showing who first funded each Solana address in SOL"* — i.e. the canonical open @@ -857,7 +862,8 @@ and our derived artifact. | `ashpoolin/gelato.sh` | MIT | 24 Solana CEX addresses | | `0xB10C/ofac-sanctioned-digital-currency-addresses` | MIT | `sanctioned_addresses_SOL.json` | -Ship as `labels/anchors.json`, one record per address: +The specification's record shape, one per address — the anchor set that shipped +lives in `classify.rs` rather than in a file: ```json { @@ -872,9 +878,14 @@ Ship as `labels/anchors.json`, one record per address: } ``` -Ship `mirror-provenance verify-labels`, which re-checks each anchor against the chain -(e.g. that a claimed exchange fee payer still pays for withdrawals from its claimed hot -wallets). **The anchor list must be falsifiable, not asserted.** +The specification called for `verify-labels`, re-checking each anchor against the +chain (e.g. that a claimed exchange fee payer still pays for withdrawals from its +claimed hot wallets), because **an anchor list must be falsifiable, not +asserted**. *This did not ship.* The anchor set is in `classify.rs` and every +entry cites its source, but nothing re-derives it from the chain, so a stale +anchor would go unnoticed. It is the largest unclosed gap in the label ladder +and the reason Tier-2 results are reported beside Tier-1 rather than instead of +them. **OFAC.** Use the **XML** feed, not the CSV: the CSV export omits them. `https://sanctionslistservice.ofac.treas.gov/api/PublicationPreview/exports/SDN.XML` @@ -1021,10 +1032,15 @@ the same set. - `MIRROR_RPC=replay` — runs **entirely offline** from the committed store. This is the default in CI. -Content-addressed: `data/raw/.json`; index `data/index.jsonl` mapping +Content-addressed: `data/raw/.json`, with an index mapping `(method, canonical_params) → sha256`. Canonical params = JSON with sorted keys, no whitespace. +*Specified, not shipped.* `rpc.rs` implements the `slot ≤ S` filter, the paging +and the failure taxonomy below; the on-disk cache is not built, because no run +here was large enough to need one. The runs are reproducible from their +committed sample files instead — see §7.3. + ### 7.3 On-disk format A `jsonParsed` `getTransaction` is 10–60 KB; 50k of them is ~1.5 GB, which does not @@ -1044,19 +1060,26 @@ belong in git. **Commit the projection plus the hash of the original.** } ``` -In-repo artifacts (all small): +**What shipped.** One file per run rather than the several this section +originally specified, because the runs are small enough that splitting them buys +nothing and costs a reader a join. `mirror collect` writes a single JSON +document with three top-level keys: -| file | contents | size at n=2,000 | -|---|---|---| -| `data/frame.jsonl` | sampled slots + blockhashes | ~100 KB | -| `data/sample.jsonl` | selected addresses + selection ordinal + stratum | ~200 KB | -| `data/traces.jsonl` | per-address path, edges, terminal class, rule fired, flags | ~2 MB | -| `labels/anchors.json` | Tier-2 anchors | ~100 KB | -| `manifest.json` | §7.5 | ~4 KB | +| key | contents | +|---|---| +| `manifest` | §7.5 — endpoint, archival probe, collection time, every threshold, the RPC call count, and the exclusion and ambiguity rates | +| `chains` | one traced funding chain per sampled address: the path, the edges, the terminal class, the rule that fired, and the failure flags | +| `facts` | the sampled address set, in selection order | -Bulk projections go to a tagged GitHub Release with checksums recorded in-repo. Ship -`--verify-projection`, which re-fetches originals and re-derives the projection -byte-for-byte. +The runs published here are in `data/sample-*.json` — around 200 KB each at +n ≈ 80–100 traced chains, which is small enough to commit whole. `mirror +analyze` reads one of these and computes every metric in §2 from it, so a reader +can rerun the analysis without touching an RPC endpoint, and `mirror compare` +takes two. + +The record/replay design in §7.2 exists so that a bigger collection could ship +its raw responses separately with checksums recorded in-repo. No run here is +large enough to need that. ### 7.4 `first_funded_by` as cross-check, not collection path @@ -1114,8 +1137,12 @@ maintained `goog_blockchain_*` datasets cover nine chains and do not include Sol } ``` -`mirror-provenance verify` recomputes every published number from the committed store -and asserts equality. Wire it into CI. +What shipped instead is `mirror analyze `: it recomputes every published +number for a run from the committed file and touches no network, so any figure in +`docs/MEASUREMENT_LOG.md` can be re-derived from the artifact beside it. It is not +wired into CI, because the runs are data rather than code — a change to +`metrics.rs` that moved a published number would be caught by the §2.7 reference +vectors, which are. --- @@ -1209,7 +1236,7 @@ least useful explanation. A harness is built scenario by scenario; the favourabl scenario is stubbed first to get the plumbing running; the stub returns the answer the author expects; and nothing downstream ever fails, because a tautology cannot fail. -**Three tells a reviewer can check in thirty seconds**, and which we must never produce: +**Three tells a reader can check in thirty seconds**, and which no honest report produces: 1. a literal `if scenario == X { return }` inside a metric; 2. every cell in the favourable column being an exact integer while the unfavourable @@ -1321,8 +1348,9 @@ beside non-zero amount/timing/fee-payer figures, plus the no-pool control. **Sound: partially, with a sharp ceiling.** `λ(D) = "mirror-pool"` for every pool-funded wallet, so the provenance partition over pool-funded wallets is a single class. That is -real, and it is the primitive the other bounty repos want, since their admitted -number-one problem is the common-funder graph. +real, and it is the primitive any decoy-based design needs, because the +common-funder graph is what collapses a decoy set: decoys funded from one wallet +share a provenance class with each other and not with the member. **But there is an exact bound, and it should be stated as a theorem:** @@ -1350,55 +1378,36 @@ anonymity.** That is a genuinely useful service and an honest deflation of the p informative — it says the recipient wanted privacy. (c) maximizes anonymity *within* the pool population while maximizing the visibility of *belonging to* it. Against the question "is this user privacy-seeking?" it increases leakage to 1 bit while decreasing - it on "which user". State this ourselves before a reviewer does. + it on "which user". It is stated here because a method that only reports the + channel it improves is not a method. **Tautology risk: MEDIUM-HIGH** if measured as "all pool-funded wallets have class = pool" (definitional). Not a tautology if measured as: - **the ceiling test** — does realized class size equal the pool's effective-k, or does it collapse toward 1 under amount/timing sub-partitioning? -- **a cross-repo A/B** — offer it as an API to the other two bounty repos, run our tracer - on their decoy sets *before* (common funder) and *after* (pool-funded), and report both - effective-k values from the same binary. Hard to fake, easy to check, and it makes - their data our evidence. +- **an A/B against a decoy-based design** — run the tracer over the same decoy set + *before* (funded from one wallet) and *after* (pool-funded), and report both + effective-k values from the same binary. Hard to fake and easy to check, because + the only thing that changed between the two numbers is the funding path. --- ## 10. Implementation -### 10.1 Build order - -Target crate: `crates/mirror-provenance`. - -1. `metrics.rs` — §2 as pure functions over class-size multisets, with the §2.7 reference - vectors, the ordering invariant, and the negative control. **No RPC, no scenario - parameter, no I/O.** This is the file a reviewer reads first; it must be obviously - honest. -2. `edges.rs` — §4.1 balance-delta extractor, with a committed mainnet fixture pinning the - verified `accountKeys` / `preBalances` alignment for a v0 lookup-table transaction. -3. `rpc.rs` — §6.1 preconditions, §7.2 record/replay, `slot ≤ S` filter, `before`-cursor - paging with explicit truncation flags, §6.2 failure taxonomy, fail-closed reporting. -4. `labels.rs` — §5 ladder with nesting enforced by types, Tier-0/1 derivation, - `labels/anchors.json`, `verify-labels`. -5. `trace.rs` — §4.3–4.6 birth-edge traversal with the §4.7 parameters. -6. `sample.rs` — §3 block-sampled frame, epoch stratification, blockhash seed, stratified - cluster bootstrap. -7. `mirror-provenance verify` in CI, running in `replay` mode. - -### 10.2 Publication order +### 10.1 What a reader should check first -1. `ρ` — the loss factor -2. the `eff_k(k)` curve with CIs -3. the §5.6 label-resolution ladder with the §2.6 bracket -4. the class-size CCDF -5. the unresolved and failure census -6. the adversary model -7. the limitations +`metrics.rs` is §2 as pure functions over class-size multisets — the §2.7 reference +vectors, the ordering invariant and the negative control, with no RPC, no scenario +parameter and no I/O. It is the file to read first, because every number this +method reports passes through it and nothing in it depends on which chain the +data came from. -**The differentiator is not a smaller number than anyone else's. It is that a reviewer -can tell which direction our error goes.** +The claim that matters is not a smaller number than anyone else's. It is that a +reader can tell **which direction the error goes**, which is why the unresolved +and failure census (§6) is reported beside every estimate rather than after it. -### 10.3 Prior art, and where this sits in it +### 10.2 Prior art, and where this sits in it The funding-provenance channel is **not novel**. Wang et al., *On How Zero-Knowledge Proof Blockchain Mixers Improve, and Worsen User Privacy*, WWW 2023, Heuristic H4 "Intermediary @@ -1459,7 +1468,7 @@ contraction) → Androulaki et al., FC 2013 (change addresses) → Meiklejohn et theirs). For the account model the right citation is **Victor, FC 2020, deposit-address reuse**, the direct ancestor of what we are doing. -### 10.4 Structural template +### 10.3 Structural template Huseynov, Shahzaib, Seres & Tapolcai, *A Tattered Cloak of Invisibility: Measuring Anonymity Loss in Railgun on Ethereum*, arXiv:2606.25926 (June 2026) is the closest diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 9f0ca519..57724240 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -85,7 +85,7 @@ different circuit. This one compares every element. Reproducibility is worth having and it is not security. Production needs a multi-party ceremony. The scaffolding for one is not in this -submission and we do not claim it is. +repository and we do not claim it is. ### The program is upgradeable @@ -229,7 +229,7 @@ Denominations are pool constants and public. Nothing hides quantity. And what the design is *for* is the behavioural case. The measurement, the crowd rule, the shared settlement timestamp and the CPI dispatch all exist to -make a stake or a vote unattributable. A submission that wanted a value mixer +make a stake or a vote unattributable. A design that wanted a value mixer would not need any of them. **Defeating an adversary with off-chain data.** Someone who knows a member diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index 0647034a..b36ae41c 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -314,9 +314,13 @@ fn a_real_proof_verifies_on_chain_and_records_the_spend() { let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &relay.pubkey()); let cu = env.send_expect_cu(ix, &relay); println!("submit_spend consumed {cu} compute units"); + // The headline cost of the protocol, and the reason the two phases exist. + // Bounded rather than pinned: the exact figure moves by a few thousand CU + // between runs with the bump search on a freshly generated nullifier. assert!( - cu < 200_000, - "submit_spend used {cu} CU, above the default per-instruction budget" + cu < 110_000, + "submit_spend used {cu} CU, above what it has ever measured and close \ + to the 200,000 default per-instruction budget" ); let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); @@ -1089,6 +1093,18 @@ fn a_crowd_of_members_perform_a_real_protocol_action_together() { "settled {} real CPI actions in one transaction, {cu} CU", batch.len() ); + // A bound, not a fixed figure. Settlement's cost moves by a few thousand CU + // between runs because the accounts are freshly generated each time and + // `find_program_address` searches a different number of bumps to derive each + // record's address. Pinning the exact number would produce a test that fails + // on nothing, so what is asserted is the property the design depends on: + // four CPI actions and their payouts fit in one instruction's default + // budget, with room to spare. + assert!( + cu < 60_000, + "four settled CPI actions cost {cu} CU, well above what this batch has \ + ever measured — settlement got materially more expensive" + ); for (spend, beneficiary, _) in &batch { let mut data = env.svm.get_account(spend).unwrap().data; @@ -1362,6 +1378,11 @@ fn the_pool_signs_an_action_as_its_own_authority() { "the pool signed a real CPI as authority, {} CU", meta.compute_units_consumed ); + assert!( + meta.compute_units_consumed < 50_000, + "a pool-signed action cost {} CU, well above what it has ever measured", + meta.compute_units_consumed + ); // The member is still paid in full, and the payout still leaves the vault — // the reordering changes when the callee sees the value, not who is owed it. From 0df36757c69e31baeb2dd8ea8603c4000e0599fe Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 00:04:23 -0300 Subject: [PATCH 66/83] chore: group the ignore rules by what they protect against --- .gitignore | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e10df076..ccd7bf0c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,24 @@ target/ **/target/ + +# Editor and tool state .DS_Store -*.so -!programs/**/fixtures/*.so +.claude/ +.idea/ +.vscode/ + +# Never .env keys/ -.claude/ *.keypair.json notes/ + +# Build artefacts. The one exception is the third-party program the CPI tests +# invoke: it is a fixture the suite loads, not something this repo builds. +*.so +!programs/**/fixtures/*.so + +# Vendored source, kept out of the tree sp4/ # Live-run state, not deliverable. The note ledgers hold the secrets that From c71db974947eb116b8678006358525b3ae5a0df3 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 03:16:28 -0300 Subject: [PATCH 67/83] docs: the label-resolution ladder was specified and not built, and now says so --- docs/PROVENANCE_METHOD.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/docs/PROVENANCE_METHOD.md b/docs/PROVENANCE_METHOD.md index fc240824..fc4706e2 100644 --- a/docs/PROVENANCE_METHOD.md +++ b/docs/PROVENANCE_METHOD.md @@ -11,6 +11,12 @@ quietly dropped: falsifies it does not, so Tier-2 labels rest on their cited sources alone. - **The multi-file artifact layout** (§7.3) became one JSON document per run. What shipped is described there. +- **The L0–L3 label-resolution ladder** (§5.1, §5.6). The classification rules + R1–R5 shipped and are what produce every published label. Reporting the *same* + measurement at four label granularities, and the monotonic bracket that would + come out of it, did not: the crate has no notion of a resolution rung, so the + published headline is a single figure at the resolution the R-rules give, + neither claimed nor claimable as "L2". - **The `n = 1,100` baseline frame** (§3) was not collected. The published runs are the smaller ones in `docs/MEASUREMENT_LOG.md`, and what that costs the estimate is stated there rather than here. @@ -800,6 +806,13 @@ a violation means the hierarchy was broken and the run must fail. **Headline at L2** — the finest two-sided resolution (§1.2). L0/L1 are published as the strong-adversary bound, L3 as the weak. +*Not shipped.* The crate classifies each address once, by the R-rules of §5.3, +and reports one ρ. It does not re-label the same sample at four granularities, so +there is no L-rung bracket and no run has ever asserted the monotonicity above. +The published headline carries the resolved/unresolved bracket of §2.6 instead, +which brackets a different uncertainty: how much the members that failed to +resolve could move the number, not how much a coarser labeller would. + ### 5.2 Atoms vs crowds — the two-stage pipeline `2^{H(C)}` and `2^{H(X|C)}` are complements, and which is correct depends on whether @@ -924,7 +937,14 @@ deposit-address set of uncertain provenance, the L2 effective-k moves from X to with the set not redistributed. The *size* of that movement is the most informative statement we can make about how much a free-label result understates a funded adversary. -### 5.6 The sensitivity table (required output) +### 5.6 The sensitivity table (specified, not produced) + +No command emits this table. It is kept because it states what a complete report +of this method looks like, and because the gap between it and what +`docs/MEASUREMENT_LOG.md` actually publishes is the honest measure of how far +the implementation got. The ablation rows are the more valuable half: they say +how much of the headline rests on each rule, and nothing in the runs answers +that. ``` resolution classes m ρ eff_k_shannon eff_k_minent worst coverage From cd55de2464467eec4f474ec76de31242086f955e Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 04:34:47 -0300 Subject: [PATCH 68/83] feat: the packet was never the ceiling, and anonymity you cannot give up is a liability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three things, and the first is a correction. **Ten was a property of the client, not the program.** The batch ceiling this repository published came from settling as a legacy transaction, which names every account by its full 32 bytes. A v0 transaction can name the same accounts by one byte each through a lookup table, and settle_epoch has always been compatible with that: it requires a signature from the settler and from nobody else, and a lookup table can serve any account that is not a signer. The word "legacy" appeared once in the whole repository, in a test comment, while README, USAGE and CROWD all stated ten as if it were the protocol's limit. So mirror settle now publishes a table for any batch that will not fit legacy, and a_lookup_table_takes_the_packet_out_of_the_way pins the arithmetic against the executed 1228-byte figure so the two cannot drift: sixty members fit inside a packet, six times what legacy allows, nowhere near a limit. The table is taken back down. It costs rent, but the reason to close it is that while it exists it is a public durable account listing every address the settlement is about to touch, published before the settlement lands — a permanent on-chain index of the batch, which is a strange thing for a privacy pool to accumulate one of per round. **Disclosure, to one verifier, chosen by the member.** No viewing key, no auditor role, no on-chain component and no path the protocol can be made to produce one down — a compel path that exists is a compel path that can be used. mirror disclose writes a file; mirror disclose-verify recomputes every value in it against the chain and reports ten checks separately, so a tampered field fails on its own name. Thirty-one tests, one per tamper case, each asserting which check failed rather than merely that verification did. It refuses before settlement, when the secrets are still the deposit rather than a proof of it, and it refuses when naming one action as yours would leave the rest below the pool's floor — the people that costs are not the discloser. **The submission phase, stated as the residual it is.** Settlement is one signer and one timestamp, and that is only half of what an observer sees. submit_spend is one transaction per member, publishing the beneficiary and the action in cleartext at a moment of the relay's choosing, in an order nothing shuffles, signed by a key that is the member themselves if they relay for themselves. A threat model scoped to the settled transaction is describing a smaller adversary than the one that exists. Also: a drift guard comparing the compiled verifying key against what the published seed derives, element by element, so the digest in the README is enforced rather than left to an operator remembering to run a command; cargo-deny with every advisory ignore carrying its reachability argument; and CI actions pinned to commit SHAs, because a tag is a mutable pointer that runs with this workflow's token. 256 tests. --- .github/workflows/ci.yml | 41 +- Cargo.lock | 2 + README.md | 101 +- crates/mirror-circuit/Cargo.toml | 3 + crates/mirror-cli/Cargo.toml | 3 + crates/mirror-cli/src/chain.rs | 41 + crates/mirror-cli/src/client.rs | 148 +- crates/mirror-cli/src/disclose.rs | 1549 +++++++++++++++++++ crates/mirror-cli/src/lookup.rs | 346 +++++ crates/mirror-cli/src/main.rs | 83 + crates/mirror-core/Cargo.toml | 3 + crates/mirror-provenance/Cargo.toml | 3 + deny.toml | 81 + docs/ARCHITECTURE.md | 5 +- docs/THREAT_MODEL.md | 44 +- docs/USAGE.md | 56 + programs/mirror-pool/Cargo.toml | 5 + programs/mirror-pool/tests/batch_ceiling.rs | 10 + programs/mirror-pool/tests/vk_drift.rs | 97 ++ 19 files changed, 2594 insertions(+), 27 deletions(-) create mode 100644 crates/mirror-cli/src/disclose.rs create mode 100644 crates/mirror-cli/src/lookup.rs create mode 100644 deny.toml create mode 100644 programs/mirror-pool/tests/vk_drift.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0365b32..8070c102 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,20 +9,29 @@ env: CARGO_TERM_COLOR: always RUSTFLAGS: -D warnings -# One job running `make verify`, which is the command the README tells a reader -# to run. Splitting the host checks from the SBF build let them drift: the -# end-to-end suite needs the .so, so a job without the Solana toolchain ran -# twenty tests that could only panic, and the artifact never crossed between -# runners. If CI and the documented command are the same command, they cannot -# disagree. +# Read-only by default. Nothing here publishes, comments or writes, so nothing +# here needs a token that could. +permissions: + contents: read + +# Third-party actions are pinned to commit SHAs rather than tags. A tag is a +# mutable pointer: `@v4` is whatever its owner last moved it to, and a compromised +# or retagged action runs with this workflow's token and this repository's cache. +# The version each SHA corresponds to is in the comment beside it. jobs: + # One job running `make verify`, which is the command the README tells a reader + # to run. Splitting the host checks from the SBF build let them drift: the + # end-to-end suite needs the .so, so a job without the Solana toolchain ran + # twenty tests that could only panic, and the artifact never crossed between + # runners. If CI and the documented command are the same command, they cannot + # disagree. verify: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Cache cargo - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: | ~/.cargo/registry @@ -31,7 +40,7 @@ jobs: key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Cache the Solana toolchain - uses: actions/cache@v4 + uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4 with: path: ~/.local/share/solana key: ${{ runner.os }}-solana-stable @@ -47,3 +56,17 @@ jobs: - name: make verify run: make verify + + # The dependency tree is part of the threat surface: a compromised transitive + # crate in the proving path sees the witness and one in the client sees the + # note. Separate from `verify` because it needs no Solana toolchain and because + # a supply-chain failure should be legible as one rather than buried in a test + # log. `deny.toml` carries the policy, and every exception in it is written + # down with the reason it is an exception. + supply-chain: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: EmbarkStudios/cargo-deny-action@3c6349835b2b7b196a839186cb8b78e02f7b5f25 # v2.1.1 + with: + command: check advisories bans licenses sources diff --git a/Cargo.lock b/Cargo.lock index 745b4be2..048fecce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1865,9 +1865,11 @@ version = "0.1.0" dependencies = [ "ark-std 0.5.0", "groth16-solana", + "hex", "litesvm", "mirror-circuit", "mirror-core", + "sha2 0.10.9", "solana-keypair", "solana-message", "solana-program", diff --git a/README.md b/README.md index cbf1f32b..5713f629 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,16 @@ Then: ``` git clone https://github.com/solanabr/mirror-pool && cd mirror-pool -make verify # fmt, clippy -D warnings, build-sbf, 216 tests +make verify # fmt, clippy -D warnings, build-sbf, 256 tests ``` +Nothing in that command needs a network, an API key or an account with anybody, +and it is the whole check — there is no second suite, no optional extra, and no +step that only works on one CPU architecture. If you would rather look than +build, the program is live on devnet at +[`8H3cYoiAA9LM36c…`](https://explorer.solana.com/address/8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa?cluster=devnet) +and every claim below links to the transaction that backs it. + `make verify` is the whole check, and it is the same command CI runs — see [`.github/workflows/ci.yml`](.github/workflows/ci.yml), which invokes `make verify` and nothing else, so the two cannot drift apart. On a cold clone it @@ -117,9 +124,9 @@ not say. There is a section below of things we deliberately do not claim. | `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain. | | `crates/mirror-circuit` | R1CS gadget, membership circuit, prover, key export. | | `crates/mirror-provenance` | The funding-provenance measurement. | -| `crates/mirror-cli` | The tool. `init-pool`, `note-new`, `deposit`, `tree`, `spend`, `settle` for members; `setup`, `verify-setup`, `soak`, `crowd` for operators; `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection` for the measurement. | +| `crates/mirror-cli` | The tool. `init-pool`, `note-new`, `deposit`, `tree`, `spend`, `settle`, `disclose`, `disclose-verify` for members; `setup`, `verify-setup`, `soak`, `crowd`, `close-table` for operators; `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection` for the measurement. | -**216 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**256 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -166,12 +173,53 @@ The relay signs and the member never does, so no member key appears on chain after the deposit. Below the crowd floor, `settle` says what it is waiting for and why rather than returning an error code. +### Giving up your anonymity, on purpose, to one person + +Anonymity you cannot surrender deliberately is a liability rather than a feature: +at some point a member has to show an exchange or an accountant that a particular +action was theirs. The usual answer is a viewing key or an auditor role, and both +are standing capabilities somebody else holds — a compel path that exists is a +compel path that can be used. + +There is none here. `mirror disclose` writes a file the member hands to **one +verifier they chose**, and `mirror disclose-verify` checks it by recomputing +every value in it: the commitment and the nullifier from the member's secrets, +the record's address from that recomputed nullifier, the accumulator from chain +history checked against the pool's own root, and the action read out of the +record. Ten checks, each reported separately, and one that cannot be completed is +a failure rather than a silence. A file whose stated nullifier disagrees with +what the secrets produce fails on that check while the others still pass, which +tells the verifier what was tampered with rather than merely that something was. + +No on-chain component, no auditor key, no protocol path that can be made to +produce one. Two things guard the member instead: + +- **It refuses before settlement.** The disclosure carries the note's secrets, + and before the nullifier is burnt those secrets *are* the deposit — anyone + holding them can prove membership and redirect the payout. After settlement + they authorise nothing and only demonstrate the link, which is the thing being + disclosed on purpose. +- **It refuses when it would cost the others too much.** Naming one action as + yours removes you as a candidate for every other action in the pool. If that + leaves the remaining set below the pool's floor, the command stops and says + what it would cost, and the override is a flag the member has to type out. The + gate is advisory — nobody can be stopped from disclosing out of band — and it + exists so the cost is visible at the moment it is paid, by the person not + paying it. + +Thirty-one tests cover it, one per tamper case, and each asserts *which* check +failed rather than merely that verification did. + ### How large a crowd fits in one settlement -It depends on what the crowd is doing, and the answer is measured in every case -rather than estimated: +Two answers, and the difference between them is a transaction format rather than +anything about the program. -| the batch | members per settlement | bytes | taken | +**With no setup at all**, settlement is a legacy transaction that names every +account by its full 32 bytes, and the 1232-byte packet is what stops it. That +floor is measured for each shape of action, not estimated: + +| the batch | members | bytes | taken | |---|---|---|---| | plain payments | **10** | 1228 | settled in litesvm | | stake delegations, everyone to the same validator | **7** | 1140 | settled in litesvm | @@ -183,7 +231,28 @@ settled 10 spends in one transaction: 1228 bytes (4 to spare), 19545 CU of 20000 while the SVM settled the same batch in 24158 CU, so compute is not the constraint ``` -The **packet size** binds in all three. Each spend brings accounts nobody else +**With an address lookup table**, the same accounts are named by one byte each +and the packet stops being the thing in the way at all. `mirror settle` publishes +a table automatically for any batch that will not fit legacy, and +`a_lookup_table_takes_the_packet_out_of_the_way` pins the arithmetic against the +executed 1228-byte figure above so the two cannot drift apart: sixty members +still fit inside a packet, six times what legacy allows and nowhere near a limit. + +Nothing in the program changes for this. `settle_epoch` requires a signature from +the settler and from nobody else, and a lookup table can serve any account that +is not a signer — so the ceiling was always a property of what the client chose +to build, and ten is the floor rather than the maximum. + +**The table is not free, and it is taken back down.** It costs four extra +transactions, a slot of latency, and rent — and, more to the point, while it +exists it is a public durable account listing every address the settlement is +about to touch, published *before* the settlement lands. Leaving one behind per +batch would turn a one-transaction event into a permanent on-chain index of the +batch, which is a strange thing for a privacy pool to accumulate. So settlement +deactivates it immediately and `mirror close-table` reclaims the rent and removes +the list once the runtime's cooldown has passed. + +Under legacy, the **packet size** binds in all three rows. Each spend brings accounts nobody else shares — its record, its beneficiary, its relay — so a payment costs about 99 bytes a member, and a call costs more because it also names its callee and the callee's accounts. @@ -349,6 +418,17 @@ symptom — proofs that verify nowhere — points at everything except the hash. ## The measurement +**Every input is somebody else's chain data.** Nothing here is simulated, and +that distinction is load-bearing rather than stylistic: an anonymity number +computed from a protocol's own parameters is a statement about arithmetic — it +comes out however the formula says it must, and a hostile reviewer can derive it +without running anything. The interesting question is what a real funding graph +does to a real anonymity set, and the only way to answer it is to go and read +one. So `mirror collect` is the one networked command, it points at pools this +project does not control, and the artifacts it produced are committed so the +analysis can be rerun offline against exactly the bytes that produced the +headline. + Four commands, two passes, and the split is the point: ``` @@ -536,7 +616,10 @@ being measured. ## Deployment -Live on **devnet** at `8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa`. The whole +Live on **devnet** at +[`8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa`](https://explorer.solana.com/address/8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa?cluster=devnet). +Every signature below is a link — the claims in this section are meant to be +read off the cluster rather than off this page. The whole lifecycle ran there against a real validator — pool creation, deposits, spends each carrying a Groth16 proof verified by the deployed program's own syscall, and a settlement that closed the vault to its rent-exempt minimum to the lamport. @@ -629,5 +712,5 @@ reporting. | `docs/THREAT_MODEL.md` | The adversary, what holds, and every place it stops. | | `docs/PROOF.md` | Devnet signatures for every flow, and the rejections. | | `docs/CROWD.md` | Six members delegating to six different validators in one devnet transaction, and what divergence costs. | -| `docs/USAGE.md` | The six member-facing commands, end to end, with real devnet output. | +| `docs/USAGE.md` | The member-facing commands, end to end, with real devnet output. | | `docs/PLAN.md` | The design as decided, and where the shipped protocol departs from it. | diff --git a/crates/mirror-circuit/Cargo.toml b/crates/mirror-circuit/Cargo.toml index 772dce63..4c334728 100644 --- a/crates/mirror-circuit/Cargo.toml +++ b/crates/mirror-circuit/Cargo.toml @@ -2,6 +2,9 @@ name = "mirror-circuit" version.workspace = true edition.workspace = true +# Not published to crates.io: these crates are built from this repository, and a +# registry copy would be a second source of truth for the same code. +publish = false license.workspace = true # Host-only crate: proving, key generation and the R1CS gadget. Never linked diff --git a/crates/mirror-cli/Cargo.toml b/crates/mirror-cli/Cargo.toml index 57f5e64f..43ba790c 100644 --- a/crates/mirror-cli/Cargo.toml +++ b/crates/mirror-cli/Cargo.toml @@ -2,6 +2,9 @@ name = "mirror-cli" version.workspace = true edition.workspace = true +# Not published to crates.io: these crates are built from this repository, and a +# registry copy would be a second source of truth for the same code. +publish = false license.workspace = true [[bin]] diff --git a/crates/mirror-cli/src/chain.rs b/crates/mirror-cli/src/chain.rs index 3a5009fe..983bb0f6 100644 --- a/crates/mirror-cli/src/chain.rs +++ b/crates/mirror-cli/src/chain.rs @@ -104,6 +104,47 @@ impl Chain { Ok(sig) } + /// Sends a versioned transaction, which is the only kind that can resolve + /// accounts through a lookup table. + /// + /// Separate from `send` rather than generic over both, because the wire + /// encodings differ and a legacy transaction silently serialized as + /// versioned — or the reverse — produces a signature verification failure + /// with nothing in it that points at the encoding. + pub fn send_versioned( + &self, + tx: &solana_transaction::versioned::VersionedTransaction, + ) -> Result { + let wire = bincode::serialize(tx)?; + let encoded = base64::engine::general_purpose::STANDARD.encode(&wire); + let sig = self.call( + "sendTransaction", + serde_json::json!([ + encoded, + { "encoding": "base64", "preflightCommitment": "confirmed" } + ]), + )?; + let sig = sig + .as_str() + .ok_or_else(|| anyhow!("signature missing"))? + .to_string(); + self.confirm(&sig)?; + Ok(sig) + } + + /// The cluster's current slot. + /// + /// Needed because a lookup table's address is derived from a *recent* slot, + /// and the runtime refuses one that is not. A slot read from anywhere but + /// the cluster about to receive the transaction is a guess. + pub fn slot(&self) -> Result { + let v = self.call( + "getSlot", + serde_json::json!([{ "commitment": "confirmed" }]), + )?; + v.as_u64().ok_or_else(|| anyhow!("slot missing")) + } + fn confirm(&self, signature: &str) -> Result<()> { for _ in 0..40 { let v = self.call( diff --git a/crates/mirror-cli/src/client.rs b/crates/mirror-cli/src/client.rs index 108ea21c..d41e53ec 100644 --- a/crates/mirror-cli/src/client.rs +++ b/crates/mirror-cli/src/client.rs @@ -8,6 +8,7 @@ use crate::chain::Chain; use crate::history::{self, History}; +use crate::lookup; use crate::note::StoredNote; use anyhow::{anyhow, Context, Result}; use ark_std::rand::SeedableRng; @@ -526,7 +527,36 @@ pub fn settle( .pack(), metas, ); - let sig = send(chain, ix, &[settler])?; + + // A legacy transaction names every account by its full 32 bytes, so a batch + // outgrows the 1232-byte packet at ten members. Rather than settle ten and + // leave the rest, a batch that does not fit is settled through a lookup + // table, which names the same accounts by one byte each. + // + // Legacy stays the default for batches that fit, and that is deliberate: a + // table costs four extra transactions, a slot of latency and rent, and none + // of it buys anything for a batch already inside the packet. + let sig = match legacy_settlement_bytes(&ix, &settler.pubkey()) { + bytes if bytes <= PACKET_DATA_SIZE => { + println!(); + println!( + "settling {} spends in one legacy transaction, {bytes} bytes", + ready.len() + ); + send(chain, ix, &[settler])? + } + bytes => { + println!(); + println!( + " {} spends do not fit a legacy transaction: {bytes} bytes, {} over the \ + {PACKET_DATA_SIZE}-byte packet.", + ready.len(), + bytes - PACKET_DATA_SIZE + ); + println!(" Settling through a lookup table instead."); + settle_through_lookup_table(chain, ix, settler)? + } + }; println!(); println!("settled {} spends in one transaction", ready.len()); println!(" signature {sig}"); @@ -536,6 +566,122 @@ pub fn settle( Ok(()) } +/// The 1280-byte IPv6 minimum MTU, less a 40-byte IPv6 header and an 8-byte +/// fragment header. +const PACKET_DATA_SIZE: usize = 1280 - 40 - 8; + +/// What this instruction would weigh as a legacy transaction signed by one key. +fn legacy_settlement_bytes(ix: &Instruction, payer: &Pubkey) -> usize { + let message = solana_message::Message::new(std::slice::from_ref(ix), Some(payer)); + 1 + 64 + message.serialize().len() +} + +/// Publishes a lookup table, settles through it, and then takes it back down. +/// +/// The table is the interesting part of the cost, and not only because of the +/// rent. While it exists it is a public, durable account listing every address +/// the settlement is about to touch — published *before* the settlement lands, +/// signed by the settler. Leaving it there would turn a one-transaction event +/// into a permanent on-chain index of the batch, which is a strange thing for a +/// privacy pool to leave behind. +/// +/// So it is deactivated immediately, and closing it — which returns the rent and +/// removes the list — is reported as the errand it is: the runtime enforces a +/// cooldown of roughly `DEACTIVATION_COOLDOWN_SLOTS` slots first, because a +/// transaction already in flight may still be resolving against the table. +fn settle_through_lookup_table( + chain: &Chain, + ix: Instruction, + settler: &Keypair, +) -> Result { + use solana_message::{v0, AddressLookupTableAccount, VersionedMessage}; + use solana_transaction::versioned::VersionedTransaction; + + let authority = settler.pubkey(); + let addresses = lookup::addresses_for(&ix.accounts, &ix.program_id); + + let recent_slot = chain.slot()?; + let (create_ix, table) = lookup::create(&authority, &authority, recent_slot)?; + send(chain, create_ix, &[settler])?; + println!(" lookup table {table}"); + + for chunk in addresses.chunks(lookup::ADDRESSES_PER_EXTEND) { + let extend_ix = lookup::extend(&table, &authority, &authority, chunk)?; + send(chain, extend_ix, &[settler])?; + } + println!(" {} addresses published", addresses.len()); + + // A table cannot be used in the slot it was extended in: the runtime resolves + // it against a slot strictly later than the last write. Waiting for the + // cluster to move on is not politeness, it is the difference between a + // settlement that lands and one rejected for an address the table already + // holds. + let extended_at = chain.slot()?; + while chain.slot()? <= extended_at { + std::thread::sleep(std::time::Duration::from_millis(400)); + } + + let alt = AddressLookupTableAccount { + key: table, + addresses, + }; + let message = v0::Message::try_compile(&authority, &[ix], &[alt], chain.latest_blockhash()?) + .map_err(|e| anyhow!("compiling the settlement against the lookup table: {e}"))?; + let tx = VersionedTransaction::try_new(VersionedMessage::V0(message), &[settler]) + .map_err(|e| anyhow!("signing the settlement: {e}"))?; + let bytes = bincode::serialize(&tx)?.len(); + println!(" settlement is {bytes} bytes of {PACKET_DATA_SIZE}, one signature"); + let sig = chain.send_versioned(&tx)?; + + send(chain, lookup::deactivate(&table, &authority)?, &[settler])?; + println!( + " table deactivated. Close it after ~{} slots to reclaim the rent and remove", + lookup::DEACTIVATION_COOLDOWN_SLOTS + ); + println!(" the published address list:"); + println!(" mirror close-table --table {table}"); + Ok(sig) +} + +/// Takes a lookup table back down, returning its rent and removing the address +/// list it published. +/// +/// Separate from settling because the runtime will not allow it to be the same +/// errand: a table must sit deactivated for a cooldown before it can close, so +/// that a transaction already in flight cannot have the table pulled out from +/// under it. Deactivation happens at settlement; this is the second half. +/// +/// Running it is optional in the sense that nothing breaks if nobody does, and +/// not optional in the sense that skipping it leaves rent stranded and, more to +/// the point, leaves a permanent public list of every address that settlement +/// touched. +pub fn close_table(chain: &Chain, table: &Pubkey, authority: &Keypair) -> Result<()> { + let before = chain.balance(&authority.pubkey())?; + let ix = lookup::close(table, &authority.pubkey(), &authority.pubkey())?; + match send(chain, ix, &[authority]) { + Ok(sig) => { + let reclaimed = chain.balance(&authority.pubkey())?.saturating_sub(before); + println!("closed {table}"); + println!(" signature {sig}"); + println!(" reclaimed {reclaimed} lamports, and the published address list is gone"); + Ok(()) + } + Err(e) => { + let message = e.to_string(); + if message.contains("not been deactivated") || message.contains("still in use") { + Err(anyhow!( + "{table} is not closable yet. A deactivated table waits about {} slots \ + before it can be closed, because a transaction already in flight may \ + still be resolving against it. Try again shortly.", + lookup::DEACTIVATION_COOLDOWN_SLOTS + )) + } else { + Err(e) + } + } + } +} + /// Creates a note and writes it, printing what to do next. pub fn note_new(denomination: u64, path: &Path) -> Result<()> { let note = crate::note::generate(denomination)?; diff --git a/crates/mirror-cli/src/disclose.rs b/crates/mirror-cli/src/disclose.rs new file mode 100644 index 00000000..172992c8 --- /dev/null +++ b/crates/mirror-cli/src/disclose.rs @@ -0,0 +1,1549 @@ +//! Voluntary disclosure: proving to one chosen verifier that a settled action +//! was yours. +//! +//! Everything about *what happened* is already public. A spend record PDA is +//! derived from the nullifier and holds, in cleartext, the beneficiary, the +//! selector, the target program, the relay fee, the payload and whether it +//! settled. The pool hides exactly one thing: **which member asked for it.** +//! +//! So a disclosure is not an audit trail, an escrow, or a key anyone else holds. +//! It is a member handing a file to a counterparty of their own choosing — an +//! exchange, a tax authority, a court, a friend — that lets that counterparty +//! recompute the link for themselves. There is no on-chain component, no +//! auditor key, and no path by which the protocol can be made to produce one. +//! That is deliberate: a compel path that exists is a compel path that can be +//! used, and a pool with one is a pool whose members are anonymous only until +//! somebody with standing asks. +//! +//! The verifier is not asked to trust the file. Every field in it is either +//! recomputed from the secrets or read off the cluster; a disclosure whose +//! stated nullifier disagrees with `H1(k)` fails rather than being believed. +//! See [`verify`]. +//! +//! ## Why this is safe after settlement and catastrophic before +//! +//! `(k, r)` is the whole authority over a note. Before the note is spent, the +//! secrets *are* the money: anyone holding them can produce the membership +//! proof and direct the payout wherever they like. Disclosing an unspent note +//! is therefore not a disclosure at all — it is handing over the deposit, and +//! the recipient can spend it before the discloser finishes explaining what the +//! file was for. +//! +//! Once the nullifier is burnt and the record is settled, `(k, r)` authorises +//! nothing. The replay guard is the *existence* of the spend record, so a second +//! spend of the same note fails at account creation and never reaches the +//! verifier. What is left in the secrets is only the ability to demonstrate the +//! link — which is the thing being disclosed on purpose. +//! +//! [`build`] refuses to write a disclosure for a note that is not settled. That +//! is not a policy check that a determined member can be talked out of; it is +//! the one mistake in this module that costs somebody their deposit. + +use crate::chain::Chain; +use crate::client; +use crate::history; +use crate::note::StoredNote; +use anyhow::{anyhow, Context, Result}; +use mirror_core::{Field, MerkleTree, Note}; +use mirror_pool_program::{ + pda::{pool_address, spend_address}, + spend::{Spend, STATUS_SETTLED}, +}; +use serde::{Deserialize, Serialize}; +use solana_program::pubkey::Pubkey; +use std::path::Path; + +/// The flag that constructs a [`DisclosureOverride`], named in one place so the +/// refusal message can tell a reader exactly what to type. +pub const ACKNOWLEDGE_FLAG: &str = "--i-accept-the-cost-to-others"; + +// --------------------------------------------------------------------------- +// The file + +/// A member's claim that one settled action was theirs, in the form a verifier +/// can check. +/// +/// Plain JSON with base58 pubkeys and hex byte strings, for the same reason the +/// note file is: it is meant to be read by the person receiving it, not only +/// parsed by this binary. A verifier who does not trust this tool should be able +/// to see what is being claimed and check it with their own code. +/// +/// It carries what a verifier needs and nothing that helps a third party. Every +/// field here is either already public on chain or is a secret that settlement +/// has already spent. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct Disclosure { + /// The pool program, base58. + pub program: String, + /// The pool account, base58. Derived from the program and the denomination, + /// and [`verify`] rederives it rather than trusting this field. + pub pool: String, + pub denomination: u64, + /// Where the note sits in the accumulator, counting from the pool's first + /// deposit. + pub leaf_index: u64, + /// `H3(k, r, denom_tag)`, 32 bytes big-endian hex. + pub commitment: String, + /// Nullifier preimage, 32 bytes big-endian hex. + /// + /// This is the secret that authorised the spend, and it is published here + /// **only because the spend already happened**. Its nullifier is burnt, the + /// spend record exists, and the record's existence is what refuses a second + /// spend — so the secret no longer moves any money. The same two lines in a + /// file written before settlement would hand the reader the deposit. + pub k: String, + /// Blinding factor, 32 bytes big-endian hex. Safe for exactly the same + /// reason `k` is, and worthless without it. + pub r: String, + /// `H1(k)`, 32 bytes big-endian hex. Already public — it was an argument to + /// the spend instruction — and repeated here so a reader can see the link + /// being claimed without running anything. + pub nullifier: String, + /// The spend record account, base58. + pub spend_record: String, + /// What the member is claiming they asked the pool to do. + pub action: ClaimedAction, +} + +/// The action a disclosure claims, field for field as the record holds it. +/// +/// Restated here rather than left implicit in "go read the record" because the +/// claim is the point: a member is asserting *this* payout was theirs, and a +/// verifier compares the assertion against the chain rather than reading the +/// chain and being told what it means. +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct ClaimedAction { + /// Who received the outcome, base58. + pub beneficiary: String, + /// Which kind of action the pool executed. + pub selector: u64, + /// The program the pool invoked, base58. All zeroes for a plain transfer. + pub target_program: String, + /// Taken out of the denomination, never added to it. + pub relay_fee: u64, + /// Instruction data for the invocation, hex. Empty for a plain transfer. + pub payload: String, +} + +impl Disclosure { + pub fn read(path: &Path) -> Result { + let text = std::fs::read_to_string(path) + .with_context(|| format!("reading the disclosure at {}", path.display()))?; + serde_json::from_str(&text).with_context(|| format!("parsing {}", path.display())) + } + + /// Writes the disclosure, overwriting if one is already there. + /// + /// Unlike a note file, this is regenerable from the note at any time, so + /// refusing to overwrite would protect nothing and would strand a member who + /// ran the command twice. + pub fn write(&self, path: &Path) -> Result<()> { + let text = serde_json::to_string_pretty(self)?; + std::fs::write(path, text) + .with_context(|| format!("writing the disclosure to {}", path.display()))?; + Ok(()) + } +} + +// --------------------------------------------------------------------------- +// What the chain says + +/// The spend record as the chain holds it, reduced to the fields a disclosure +/// claims. +#[derive(Clone, Debug)] +pub struct SpendRecordView { + /// `STATUS_SETTLED`, not merely present. A pending record means the action + /// has not executed, and a member who discloses one is disclosing a note + /// whose secrets still authorise a payout. + pub settled: bool, + pub pool: [u8; 32], + pub beneficiary: [u8; 32], + pub selector: u64, + pub target_program: [u8; 32], + pub relay_fee: u64, + pub payload: Vec, +} + +/// Everything [`verify`] needs from the cluster, gathered in one place. +/// +/// Split out from the RPC so the verification logic is a pure function of a +/// value a test can construct. That is not a testing convenience: it is what +/// makes the tamper matrix below exhaustive, because every one of these fields +/// is something an attacker controls if they control the endpoint, and each one +/// must be shown to break exactly one check. +#[derive(Clone, Debug)] +pub struct ChainView { + /// The denomination the pool account reports, which is not necessarily the + /// one the disclosure claims. + pub denomination: u64, + pub k_floor: u32, + /// The pool's current accumulator root. + pub root: [u8; 32], + /// Spends the pool counts as settled. Incremented at settlement, not at + /// submission, so it is the size of the set a disclosure shrinks. + pub settled_spends: u64, + /// Commitments in insertion order, recovered from deposit instructions. + pub leaves: Vec<[u8; 32]>, + /// `None` when no account exists at the disclosed address. + pub record: Option, +} + +/// Reads the cluster into a [`ChainView`]. +/// +/// Deliberately thin, and deliberately the only part of this module that talks +/// to a network. Anything it cannot establish is an error rather than a +/// [`ChainView`] with a plausible-looking hole in it: a rebuilt accumulator that +/// is missing leaves would fail the root check and read to a member like the +/// discloser's fault rather than the endpoint's. +pub fn observe( + chain: &Chain, + program: &Pubkey, + denomination: u64, + record: &Pubkey, + verbose: bool, +) -> Result { + let state = client::read_pool(chain, program, denomination)?; + let history = history::scan(chain, program, &state.pool, verbose)?; + if history.commitments.len() as u64 != state.deposits { + return Err(anyhow!( + "recovered {} leaves but the pool has inserted {}. The history is \ + incomplete — most likely the endpoint has pruned it — and a \ + disclosure cannot be checked against a partial accumulator. \ + `mirror check-endpoint` tests for exactly that.", + history.commitments.len(), + state.deposits + )); + } + let record = match chain.account_data(record)? { + None => None, + Some(mut data) => { + let spend = Spend::load(&mut data) + .map_err(|e| anyhow!("{record} exists but is not a spend record: {e:?}"))?; + Some(SpendRecordView { + settled: spend.status() == STATUS_SETTLED, + pool: spend.pool(), + beneficiary: spend.beneficiary(), + selector: spend.selector(), + target_program: spend.target_program(), + relay_fee: spend.relay_fee(), + payload: spend.payload().to_vec(), + }) + } + }; + Ok(ChainView { + denomination: state.denomination, + k_floor: state.k_floor, + root: state.root, + settled_spends: state.spends, + leaves: history.commitments, + record, + }) +} + +// --------------------------------------------------------------------------- +// The co-participant gate + +/// What disclosing costs the members who did not. +/// +/// A pool's privacy is joint property. Every settled action is a candidate for +/// every member, and a member who names one of them as theirs removes it from +/// everyone else's cover: the remaining actions are now shared among a smaller +/// set. The discloser pays nothing for this — they have already chosen — and the +/// people who pay are not in the room. +#[derive(Clone, Copy, Debug)] +pub struct CoParticipantCost { + pub settled_spends: u64, + pub k_floor: u32, + /// Settled actions still unattributed once this one is named. + pub remaining: u64, +} + +impl CoParticipantCost { + pub fn new(settled_spends: u64, k_floor: u32) -> Self { + CoParticipantCost { + settled_spends, + k_floor, + // Saturating rather than checked: a pool with no settled spend has + // nothing to disclose, and [`build`] refuses it earlier for the + // stronger reason that the note is unspent. + remaining: settled_spends.saturating_sub(1), + } + } + + /// Whether what is left after this disclosure is still a crowd by the + /// pool's own definition. + pub fn leaves_a_crowd(&self) -> bool { + self.remaining >= self.k_floor as u64 + } +} + +/// Deliberate consent to disclose below the pool's floor. +/// +/// The gate is **advisory and cannot be otherwise**. The secrets are the +/// member's; they can publish them in a text message, and no code here or on +/// chain can stop that. What this type buys is that the cost is visible at the +/// moment it is paid, to the person paying it on someone else's behalf, rather +/// than discovered later by the members who lost cover. +/// +/// It is a constructed type instead of a `bool` parameter for the same reason +/// the staker and the withdrawer are separate arguments elsewhere in this crate: +/// a bare `true` is one keystroke and one misread signature away from being +/// passed by a caller who never considered the question. Building this requires +/// having computed the cost first. +#[derive(Clone, Copy, Debug)] +pub struct DisclosureOverride { + acknowledged: CoParticipantCost, +} + +impl DisclosureOverride { + /// Consents to the cost described, which the caller must have computed. + pub fn acknowledging(cost: CoParticipantCost) -> Self { + DisclosureOverride { acknowledged: cost } + } + + /// The cost that was consented to, for the record this prints. + pub fn cost(&self) -> CoParticipantCost { + self.acknowledged + } +} + +// --------------------------------------------------------------------------- +// Building one + +/// Assembles a disclosure for a note this pool has already settled. +/// +/// Every field is derived rather than accepted: the leaf index is found by +/// searching the recovered leaf set for the recomputed commitment, the record +/// address is the PDA of the recomputed nullifier, and the action is copied out +/// of the record the chain holds rather than out of anything the member typed. +/// A disclosure this tool would refuse to verify is never written. +pub fn build( + program: &Pubkey, + pool: &Pubkey, + stored: &StoredNote, + view: &ChainView, + consent: Option<&DisclosureOverride>, +) -> Result { + let note = stored.note()?; + if view.denomination != stored.denomination { + return Err(anyhow!( + "this note is for denomination {} and the pool at {pool} holds {}", + stored.denomination, + view.denomination + )); + } + let commitment = note.commitment().map_err(|e| anyhow!("{e:?}"))?.to_bytes(); + let leaf_index = view + .leaves + .iter() + .position(|leaf| leaf == &commitment) + .ok_or_else(|| { + anyhow!( + "this note is not a leaf of the pool at {pool}. Either it was \ + never deposited, or it belongs to a different denomination." + ) + })? as u64; + let nullifier = note.nullifier().map_err(|e| anyhow!("{e:?}"))?.to_bytes(); + let (record_address, _) = spend_address(program, pool, &nullifier); + + // The refusal that protects the discloser rather than their co-members. An + // unspent note's secrets still authorise a payout, so a "disclosure" of one + // is a transfer of the deposit to whoever reads the file. + let record = view.record.as_ref().ok_or_else(|| { + anyhow!( + "there is no spend record at {record_address}, so this note has not \ + been spent.\n\n\ + Its secrets still authorise a payout: anyone holding them can prove \ + membership and direct the money wherever they like. Disclosing an \ + unspent note does not prove what you did with it — it gives it away.\n\n\ + Spend the note first. Once it has settled, k and r authorise nothing \ + and this file proves only what it says." + ) + })?; + if !record.settled { + return Err(anyhow!( + "the spend record at {record_address} exists but has not settled.\n\n\ + The action has not executed yet, so there is nothing to prove was \ + yours, and the note's secrets are still the only thing standing \ + between the deposit and whoever holds them. Wait for settlement — \ + `mirror settle` runs it, and so does anyone else." + )); + } + if record.pool != pool.to_bytes() { + return Err(anyhow!( + "the record at {record_address} belongs to pool {}, not {pool}. \ + A disclosure naming this pool would not verify.", + Pubkey::new_from_array(record.pool) + )); + } + + let cost = CoParticipantCost::new(view.settled_spends, view.k_floor); + if !cost.leaves_a_crowd() && consent.is_none() { + return Err(anyhow!( + "REFUSING: this pool has settled {} spend(s) and its floor is {}. \ + Naming one of them as yours leaves {} unattributed, which is below \ + the floor.\n\n\ + The people that costs are not you. Every settled action is a \ + candidate for every member; removing one narrows the guess for all \ + the rest, and they did not agree to it and will not be told.\n\n\ + If you have weighed that and still want to, pass {ACKNOWLEDGE_FLAG}. \ + Nothing here can stop you disclosing out-of-band anyway — the secrets \ + are yours. This exists so the cost is visible at the moment it is \ + paid.", + cost.settled_spends, + cost.k_floor, + cost.remaining + )); + } + + Ok(Disclosure { + program: program.to_string(), + pool: pool.to_string(), + denomination: stored.denomination, + leaf_index, + commitment: hex::encode(commitment), + k: hex::encode(note.k.to_bytes()), + r: hex::encode(note.r.to_bytes()), + nullifier: hex::encode(nullifier), + spend_record: record_address.to_string(), + action: ClaimedAction { + beneficiary: Pubkey::new_from_array(record.beneficiary).to_string(), + selector: record.selector, + target_program: Pubkey::new_from_array(record.target_program).to_string(), + relay_fee: record.relay_fee, + payload: hex::encode(&record.payload), + }, + }) +} + +// --------------------------------------------------------------------------- +// Checking one + +/// One independently reported step of a verification. +/// +/// Separate variants rather than one boolean because "this disclosure is false" +/// is not a useful thing to hand somebody. A verifier needs to know whether the +/// secrets are wrong, the leaf set is incomplete, or the record says something +/// else — those are different conversations with different people. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Check { + /// `H3(k, r, denom_tag)` equals the stated commitment. + SecretsRecomputeToTheCommitment, + /// The stated pool is the PDA for the stated denomination, and the account + /// there reports that denomination. + ThePoolMatchesTheDenomination, + /// The recomputed commitment sits at the stated leaf index. + TheCommitmentIsTheStatedLeaf, + /// The accumulator rebuilt from history has the pool's on-chain root. + TheRebuiltRootMatchesTheChain, + /// `H1(k)` equals the stated nullifier. + SecretsRecomputeToTheNullifier, + /// The stated record address is the PDA of the *recomputed* nullifier. + TheRecordAddressIsTheNullifiersPda, + /// An account exists at that address. + TheRecordExistsOnChain, + /// It settled, rather than merely being submitted. + TheRecordIsSettled, + /// Its `pool` field is this pool. + TheRecordBelongsToThisPool, + /// Its beneficiary, selector, target, fee and payload are what is claimed. + TheRecordMatchesTheClaimedAction, +} + +impl Check { + /// Every check, in the order [`verify`] reports them. + pub const ALL: [Check; 10] = [ + Check::SecretsRecomputeToTheCommitment, + Check::ThePoolMatchesTheDenomination, + Check::TheCommitmentIsTheStatedLeaf, + Check::TheRebuiltRootMatchesTheChain, + Check::SecretsRecomputeToTheNullifier, + Check::TheRecordAddressIsTheNullifiersPda, + Check::TheRecordExistsOnChain, + Check::TheRecordIsSettled, + Check::TheRecordBelongsToThisPool, + Check::TheRecordMatchesTheClaimedAction, + ]; + + /// A line a verifier can read without knowing this codebase. + pub fn name(&self) -> &'static str { + match self { + Check::SecretsRecomputeToTheCommitment => "the secrets recompute to the commitment", + Check::ThePoolMatchesTheDenomination => "the pool matches the denomination", + Check::TheCommitmentIsTheStatedLeaf => "the commitment is the stated leaf", + Check::TheRebuiltRootMatchesTheChain => "the rebuilt root matches the chain", + Check::SecretsRecomputeToTheNullifier => "the secrets recompute to the nullifier", + Check::TheRecordAddressIsTheNullifiersPda => { + "the record address is the nullifier's PDA" + } + Check::TheRecordExistsOnChain => "the spend record exists on chain", + Check::TheRecordIsSettled => "the spend record is settled", + Check::TheRecordBelongsToThisPool => "the spend record belongs to this pool", + Check::TheRecordMatchesTheClaimedAction => "the record matches the claimed action", + } + } +} + +/// One check's verdict, with what was compared. +#[derive(Debug)] +pub struct Verdict { + pub check: Check, + pub passed: bool, + /// What the check saw, phrased so a failure tells the reader what to do + /// about it rather than only that something is wrong. + pub detail: String, +} + +/// Every check and its verdict. +#[derive(Debug, Default)] +pub struct Verification { + verdicts: Vec, +} + +impl Verification { + fn record(&mut self, check: Check, outcome: Result) { + let (passed, detail) = match outcome { + Ok(detail) => (true, detail), + Err(detail) => (false, detail), + }; + self.verdicts.push(Verdict { + check, + passed, + detail, + }); + } + + pub fn verdicts(&self) -> &[Verdict] { + &self.verdicts + } + + pub fn failures(&self) -> impl Iterator { + self.verdicts.iter().filter(|v| !v.passed) + } + + /// True only when every check ran and passed. There is no partial credit: + /// a disclosure that half-verifies proves nothing. + pub fn passed(&self) -> bool { + self.verdicts.len() == Check::ALL.len() && self.verdicts.iter().all(|v| v.passed) + } +} + +/// The secrets, turned back into the two values the chain published. +struct Recomputed { + commitment: [u8; 32], + nullifier: [u8; 32], +} + +/// Every field of the disclosure that has to be parsed before it can be used. +/// +/// Held as `Result`s rather than unwrapped up front so that a field this tool +/// cannot read fails the checks that depend on it instead of aborting the whole +/// verification. A verifier handed a corrupt file should still learn which parts +/// of the claim survive. +struct Parsed { + program: Result, + pool: Result, + commitment: Result<[u8; 32], String>, + nullifier: Result<[u8; 32], String>, + record_address: Result, + beneficiary: Result, + target_program: Result, + payload: Result, String>, + secrets: Result, +} + +fn parse_pubkey(s: &str, what: &str) -> Result { + s.parse() + .map_err(|e| format!("the {what} is not a base58 pubkey: {e}")) +} + +fn parse_hex32(s: &str, what: &str) -> Result<[u8; 32], String> { + let raw = hex::decode(s).map_err(|e| format!("the {what} is not hex: {e}"))?; + raw.try_into() + .map_err(|_| format!("the {what} is not 32 bytes")) +} + +fn parse_field(s: &str, what: &str) -> Result { + let bytes = parse_hex32(s, what)?; + Field::from_bytes(bytes).map_err(|e| format!("{what} is not a canonical field element: {e:?}")) +} + +/// Recomputes the commitment and the nullifier from the disclosed secrets. +/// +/// This is the recompute-first rule in one function: the two values the rest of +/// the verification compares against come from `(k, r)` and the denomination, +/// never from the fields of the file that claim them. +fn recompute(disclosure: &Disclosure) -> Result { + let k = parse_field(&disclosure.k, "k")?; + let r = parse_field(&disclosure.r, "r")?; + let note = Note::new(k, r, Field::from_u64(disclosure.denomination)); + Ok(Recomputed { + commitment: note + .commitment() + .map_err(|e| format!("hashing the commitment: {e:?}"))? + .to_bytes(), + nullifier: note + .nullifier() + .map_err(|e| format!("hashing the nullifier: {e:?}"))? + .to_bytes(), + }) +} + +/// Rebuilds the accumulator from a recovered leaf set. +/// +/// Order matters and is the whole reason this is worth checking: the same +/// leaves inserted in a different order give a different root, so a root that +/// matches the chain's is evidence the leaf set is both complete and correctly +/// ordered — which is what makes "leaf 7 is the commitment" mean anything. +fn rebuild_root(leaves: &[[u8; 32]]) -> Result<[u8; 32], String> { + let mut tree = MerkleTree::new().map_err(|e| format!("{e:?}"))?; + for (i, leaf) in leaves.iter().enumerate() { + let field = Field::from_bytes(*leaf) + .map_err(|_| format!("recovered leaf {i} is not a canonical field element"))?; + tree.insert(field).map_err(|e| format!("{e:?}"))?; + } + tree.root() + .map(|root| root.to_bytes()) + .map_err(|e| format!("{e:?}")) +} + +fn secrets_recompute_to_the_commitment(parsed: &Parsed) -> Result { + let secrets = parsed.secrets.as_ref().map_err(Clone::clone)?; + let stated = parsed.commitment.as_ref().map_err(Clone::clone)?; + if secrets.commitment != *stated { + return Err(format!( + "the secrets commit to {}, but the disclosure states {}", + hex::encode(secrets.commitment), + hex::encode(stated) + )); + } + Ok(format!( + "H3(k, r, denom_tag) = {}", + hex::encode(secrets.commitment) + )) +} + +/// The pool is a PDA of the program and the denomination, so a disclosure that +/// names all three cannot be internally consistent by accident — and the account +/// at that address must itself report the denomination the commitment was bound +/// to, or the note being disclosed belongs to a different tree. +fn the_pool_matches_the_denomination( + disclosure: &Disclosure, + view: &ChainView, + parsed: &Parsed, +) -> Result { + let program = parsed.program.as_ref().map_err(Clone::clone)?; + let pool = parsed.pool.as_ref().map_err(Clone::clone)?; + let (derived, _) = pool_address(program, disclosure.denomination); + if derived != *pool { + return Err(format!( + "denomination {} under program {program} is pool {derived}, not {pool}", + disclosure.denomination + )); + } + if view.denomination != disclosure.denomination { + return Err(format!( + "the pool at {pool} holds denomination {}, but the disclosure claims {}", + view.denomination, disclosure.denomination + )); + } + Ok(format!( + "{pool} is the pool for {} lamports", + view.denomination + )) +} + +/// Checked against the *recomputed* commitment. A file whose commitment field +/// were trusted here would let a discloser point at somebody else's leaf. +fn the_commitment_is_the_stated_leaf( + disclosure: &Disclosure, + view: &ChainView, + parsed: &Parsed, +) -> Result { + let secrets = parsed.secrets.as_ref().map_err(Clone::clone)?; + let at = view + .leaves + .get(disclosure.leaf_index as usize) + .ok_or_else(|| { + format!( + "the disclosure claims leaf {}, but the pool's history holds only {} leaves", + disclosure.leaf_index, + view.leaves.len() + ) + })?; + if *at != secrets.commitment { + let elsewhere = view + .leaves + .iter() + .position(|leaf| leaf == &secrets.commitment); + return Err(match elsewhere { + Some(i) => format!( + "the secrets commit to leaf {i}, not to leaf {}", + disclosure.leaf_index + ), + None => format!( + "the commitment these secrets produce is not in the pool's leaf \ + set at all ({} leaves recovered)", + view.leaves.len() + ), + }); + } + Ok(format!( + "leaf {} of {} is {}", + disclosure.leaf_index, + view.leaves.len(), + hex::encode(secrets.commitment) + )) +} + +/// The check that makes the leaf index mean something. +/// +/// Without it, a verifier is trusting whoever served the history. A leaf set +/// with an insertion missing, or reordered, produces a tree in which some other +/// member's note occupies the disclosed index — and every other check here would +/// still pass. Matching the on-chain root is the only available evidence that +/// the recovered set is the set the program accumulated. +fn the_rebuilt_root_matches_the_chain(view: &ChainView) -> Result { + let rebuilt = rebuild_root(&view.leaves)?; + if rebuilt != view.root { + return Err(format!( + "the accumulator rebuilt from {} recovered leaves has root {}, but \ + the pool holds {}. The leaf set or its order is wrong, so the leaf \ + index above names nothing.", + view.leaves.len(), + hex::encode(rebuilt), + hex::encode(view.root) + )); + } + Ok(format!( + "{} leaves rebuild to {}", + view.leaves.len(), + hex::encode(rebuilt) + )) +} + +fn secrets_recompute_to_the_nullifier(parsed: &Parsed) -> Result { + let secrets = parsed.secrets.as_ref().map_err(Clone::clone)?; + let stated = parsed.nullifier.as_ref().map_err(Clone::clone)?; + if secrets.nullifier != *stated { + return Err(format!( + "H1(k) is {}, but the disclosure states {}", + hex::encode(secrets.nullifier), + hex::encode(stated) + )); + } + Ok(format!("H1(k) = {}", hex::encode(secrets.nullifier))) +} + +/// Derived from the secrets, not read out of the file. +/// +/// This is what binds the record to the note. A disclosure that could name any +/// record address would be a member pointing at whichever settled action suited +/// them; the address has to fall out of `H1(k)` and the pool. +fn the_record_address_is_the_nullifiers_pda(parsed: &Parsed) -> Result { + let secrets = parsed.secrets.as_ref().map_err(Clone::clone)?; + let program = parsed.program.as_ref().map_err(Clone::clone)?; + let pool = parsed.pool.as_ref().map_err(Clone::clone)?; + let stated = parsed.record_address.as_ref().map_err(Clone::clone)?; + let (derived, _) = spend_address(program, pool, &secrets.nullifier); + if derived != *stated { + return Err(format!( + "H1(k) in this pool is record {derived}, but the disclosure names {stated}" + )); + } + Ok(format!("{derived} is the record for H1(k)")) +} + +fn the_record_exists_on_chain(view: &ChainView, parsed: &Parsed) -> Result { + let stated = parsed.record_address.as_ref().map_err(Clone::clone)?; + match &view.record { + None => Err(format!( + "no account exists at {stated}. Either the spend was never submitted, \ + or this endpoint cannot see it." + )), + Some(_) => Ok(format!("{stated} holds a spend record")), + } +} + +/// Settled, not merely submitted. +/// +/// A pending record is a burnt nullifier and an action that has not run. The +/// member has not yet done the thing they are claiming to have done, and the +/// action may still fail at settlement. +fn the_record_is_settled(view: &ChainView) -> Result { + let record = view + .record + .as_ref() + .ok_or_else(|| "there is no record to read a status from".to_string())?; + if !record.settled { + return Err( + "the record is pending: the action was authorised but has not executed, \ + so there is nothing yet to have been done" + .to_string(), + ); + } + Ok("settled".to_string()) +} + +fn the_record_belongs_to_this_pool(parsed: &Parsed, view: &ChainView) -> Result { + let pool = parsed.pool.as_ref().map_err(Clone::clone)?; + let record = view + .record + .as_ref() + .ok_or_else(|| "there is no record to read a pool from".to_string())?; + if record.pool != pool.to_bytes() { + return Err(format!( + "the record names pool {}, not {pool}", + Pubkey::new_from_array(record.pool) + )); + } + Ok(format!("the record names {pool}")) +} + +/// Every field the action binding covered, compared one at a time. +/// +/// Reported field by field rather than as a single equality because the fields +/// mean different things to a verifier: a wrong beneficiary is a different claim +/// entirely, and a wrong relay fee is an arithmetic disagreement about the same +/// one. +fn the_record_matches_the_claimed_action( + disclosure: &Disclosure, + view: &ChainView, + parsed: &Parsed, +) -> Result { + let record = view + .record + .as_ref() + .ok_or_else(|| "there is no record to compare the action against".to_string())?; + let beneficiary = parsed.beneficiary.as_ref().map_err(Clone::clone)?; + let target = parsed.target_program.as_ref().map_err(Clone::clone)?; + let payload = parsed.payload.as_ref().map_err(Clone::clone)?; + + let mut wrong: Vec = Vec::new(); + if record.beneficiary != beneficiary.to_bytes() { + wrong.push(format!( + "beneficiary: the record paid {}, the disclosure claims {beneficiary}", + Pubkey::new_from_array(record.beneficiary) + )); + } + if record.selector != disclosure.action.selector { + wrong.push(format!( + "selector: the record holds {}, the disclosure claims {}", + record.selector, disclosure.action.selector + )); + } + if record.target_program != target.to_bytes() { + wrong.push(format!( + "target program: the record invoked {}, the disclosure claims {target}", + Pubkey::new_from_array(record.target_program) + )); + } + if record.relay_fee != disclosure.action.relay_fee { + wrong.push(format!( + "relay fee: the record holds {}, the disclosure claims {}", + record.relay_fee, disclosure.action.relay_fee + )); + } + if record.payload != *payload { + wrong.push(format!( + "payload: the record holds {}, the disclosure claims {}", + hex::encode(&record.payload), + hex::encode(payload) + )); + } + if !wrong.is_empty() { + return Err(wrong.join("; ")); + } + Ok(format!( + "selector {} paid {beneficiary}, fee {}, {} payload byte(s)", + record.selector, + record.relay_fee, + record.payload.len() + )) +} + +/// Re-derives the whole claim and reports every check separately. +/// +/// Infallible by design. A malformed disclosure produces failed checks rather +/// than an error, because "this tool could not read the file" and "this tool +/// read the file and it was false" must never be distinguishable to a caller +/// that only looks at a return code — both mean the claim is not established. +/// +/// Fail-closed throughout: a check whose inputs are missing fails, and a check +/// whose prerequisite failed fails on its own terms rather than being skipped. +/// There is no path through this function that reports a pass for something it +/// did not compare. +pub fn verify(disclosure: &Disclosure, view: &ChainView) -> Verification { + let parsed = Parsed { + program: parse_pubkey(&disclosure.program, "program"), + pool: parse_pubkey(&disclosure.pool, "pool"), + commitment: parse_hex32(&disclosure.commitment, "commitment"), + nullifier: parse_hex32(&disclosure.nullifier, "nullifier"), + record_address: parse_pubkey(&disclosure.spend_record, "spend record address"), + beneficiary: parse_pubkey(&disclosure.action.beneficiary, "beneficiary"), + target_program: parse_pubkey(&disclosure.action.target_program, "target program"), + payload: hex::decode(&disclosure.action.payload) + .map_err(|e| format!("the payload is not hex: {e}")), + secrets: recompute(disclosure), + }; + + let mut result = Verification::default(); + result.record( + Check::SecretsRecomputeToTheCommitment, + secrets_recompute_to_the_commitment(&parsed), + ); + result.record( + Check::ThePoolMatchesTheDenomination, + the_pool_matches_the_denomination(disclosure, view, &parsed), + ); + result.record( + Check::TheCommitmentIsTheStatedLeaf, + the_commitment_is_the_stated_leaf(disclosure, view, &parsed), + ); + result.record( + Check::TheRebuiltRootMatchesTheChain, + the_rebuilt_root_matches_the_chain(view), + ); + result.record( + Check::SecretsRecomputeToTheNullifier, + secrets_recompute_to_the_nullifier(&parsed), + ); + result.record( + Check::TheRecordAddressIsTheNullifiersPda, + the_record_address_is_the_nullifiers_pda(&parsed), + ); + result.record( + Check::TheRecordExistsOnChain, + the_record_exists_on_chain(view, &parsed), + ); + result.record(Check::TheRecordIsSettled, the_record_is_settled(view)); + result.record( + Check::TheRecordBelongsToThisPool, + the_record_belongs_to_this_pool(&parsed, view), + ); + result.record( + Check::TheRecordMatchesTheClaimedAction, + the_record_matches_the_claimed_action(disclosure, view, &parsed), + ); + result +} + +/// Prints the checks as a table, failures included. +/// +/// Every check is printed whether it passed or not. A report that listed only +/// the failures would leave a verifier unable to tell a claim that was checked +/// thoroughly from one that was barely checked at all. +pub fn report(disclosure: &Disclosure, result: &Verification) { + println!( + "disclosure for leaf {} of {}", + disclosure.leaf_index, disclosure.pool + ); + println!(" nullifier {}", disclosure.nullifier); + println!(" record {}", disclosure.spend_record); + println!(); + for verdict in result.verdicts() { + println!( + " {:<42} {}", + verdict.check.name(), + if verdict.passed { "PASS" } else { "FAIL" } + ); + println!(" {}", verdict.detail); + } +} + +// --------------------------------------------------------------------------- +// The two commands + +/// Writes a disclosure for a settled note. +pub fn create( + chain: &Chain, + program: &Pubkey, + note_path: &Path, + out: &Path, + acknowledge_cost: bool, +) -> Result<()> { + let stored = StoredNote::read(note_path)?; + let note = stored.note()?; + let (pool, _) = pool_address(program, stored.denomination); + let nullifier = note.nullifier().map_err(|e| anyhow!("{e:?}"))?.to_bytes(); + let (record, _) = spend_address(program, &pool, &nullifier); + + println!("rebuilding the pool from chain history:"); + let view = observe(chain, program, stored.denomination, &record, true)?; + + // The flag arrives as a bool because a command-line flag is one. It becomes + // a constructed override here, at the edge, so that nothing inside this + // module can be handed a consent it never asked for. + let cost = CoParticipantCost::new(view.settled_spends, view.k_floor); + let consent = if acknowledge_cost { + Some(DisclosureOverride::acknowledging(cost)) + } else { + None + }; + let disclosure = build(program, &pool, &stored, &view, consent.as_ref())?; + + // Checked before it is written, against the same view. A disclosure that + // would not verify is a file a member would hand over and be disbelieved + // for, and they would have no way to tell why. + let result = verify(&disclosure, &view); + if !result.passed() { + report(&disclosure, &result); + return Err(anyhow!( + "refusing to write a disclosure that does not verify against the \ + chain it was built from" + )); + } + + disclosure.write(out)?; + println!(); + println!("wrote {}", out.display()); + println!(" leaf {}", disclosure.leaf_index); + println!(" nullifier {}", disclosure.nullifier); + println!(" record {}", disclosure.spend_record); + if let Some(consent) = consent { + let cost = consent.cost(); + println!(); + println!( + "You disclosed below the floor: {} settled spend(s), floor {}, {} left", + cost.settled_spends, cost.k_floor, cost.remaining + ); + println!("unattributed. That cost was paid by the other members of this pool."); + } + println!(); + println!("This file contains k and r. They authorise nothing now — the nullifier is"); + println!("burnt and the record has settled — but they identify you to whoever holds"); + println!("it, permanently and with no way to take it back. Send it to one verifier,"); + println!("over a channel you would send a passport scan over, and to nobody else."); + println!(); + println!("The verifier checks it with: mirror disclose-verify --file "); + Ok(()) +} + +/// Checks a disclosure against the cluster and prints every verdict. +pub fn check(chain: &Chain, path: &Path) -> Result<()> { + let disclosure = Disclosure::read(path)?; + // Needed before the RPC can be pointed anywhere. A file too malformed to + // name a program cannot be checked at all, which is a failure to establish + // the claim rather than a claim that failed. + let program: Pubkey = disclosure + .program + .parse() + .map_err(|e| anyhow!("the program in {} is not a pubkey: {e}", path.display()))?; + let record: Pubkey = disclosure.spend_record.parse().map_err(|e| { + anyhow!( + "the spend record address in {} is not a pubkey: {e}", + path.display() + ) + })?; + + println!("rebuilding the pool from chain history:"); + let view = observe(chain, &program, disclosure.denomination, &record, true)?; + println!(); + let result = verify(&disclosure, &view); + report(&disclosure, &result); + println!(); + + if result.passed() { + println!( + "VERIFIED. Every value in this file was recomputed from the secrets or \ + read off the\ncluster. The holder of this note is whoever asked the pool \ + for the action above." + ); + return Ok(()); + } + Err(anyhow!( + "NOT VERIFIED: {} of {} checks failed. This file does not establish that its \ + author authorised that action — it establishes nothing at all.", + result.failures().count(), + Check::ALL.len() + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + const DENOM: u64 = 100_000_000; + /// Where the disclosed note sits in the fixture's leaf set. + const LEAF: u64 = 3; + const FEE: u64 = 5_000; + const SELECTOR: u64 = 2; + + fn program() -> Pubkey { + Pubkey::new_from_array([7u8; 32]) + } + + fn beneficiary() -> Pubkey { + Pubkey::new_from_array([11u8; 32]) + } + + fn target() -> Pubkey { + Pubkey::new_from_array([13u8; 32]) + } + + fn note() -> Note { + Note::new( + Field::from_u64(1_234_567), + Field::from_u64(7_654_321), + Field::from_u64(DENOM), + ) + } + + fn root_of(leaves: &[[u8; 32]]) -> [u8; 32] { + rebuild_root(leaves).unwrap() + } + + fn leaves_with(commitment: [u8; 32]) -> Vec<[u8; 32]> { + let mut leaves: Vec<[u8; 32]> = (1..=6u64) + .map(|i| Field::from_u64(i * 977 + 13).to_bytes()) + .collect(); + leaves[LEAF as usize] = commitment; + leaves + } + + fn record_for(pool: &Pubkey) -> SpendRecordView { + SpendRecordView { + settled: true, + pool: pool.to_bytes(), + beneficiary: beneficiary().to_bytes(), + selector: SELECTOR, + target_program: target().to_bytes(), + relay_fee: FEE, + payload: b"delegate".to_vec(), + } + } + + /// A pool that has settled comfortably above its floor, holding the note. + fn honest() -> (Disclosure, ChainView) { + let program = program(); + let (pool, _) = pool_address(&program, DENOM); + let stored = StoredNote::from_note(¬e(), DENOM).unwrap(); + let leaves = leaves_with(stored.commitment_bytes().unwrap()); + let view = ChainView { + denomination: DENOM, + k_floor: 2, + root: root_of(&leaves), + settled_spends: 5, + leaves, + record: Some(record_for(&pool)), + }; + let disclosure = build(&program, &pool, &stored, &view, None).unwrap(); + (disclosure, view) + } + + fn verify_with(mutate: impl FnOnce(&mut Disclosure, &mut ChainView)) -> Verification { + let (mut disclosure, mut view) = honest(); + mutate(&mut disclosure, &mut view); + verify(&disclosure, &view) + } + + fn failed(result: &Verification) -> Vec { + result.failures().map(|v| v.check).collect() + } + + fn detail(result: &Verification, check: Check) -> String { + result + .verdicts() + .iter() + .find(|v| v.check == check) + .expect("every check is reported") + .detail + .clone() + } + + // -- the honest cases --------------------------------------------------- + + #[test] + fn an_honest_disclosure_verifies() { + let (disclosure, view) = honest(); + let result = verify(&disclosure, &view); + assert!( + result.passed(), + "an honest disclosure failed: {:?}", + failed(&result) + ); + } + + /// The claim is only as good as its coverage, so the report must show every + /// check exactly once and in a stable order — a verifier comparing two runs + /// should be comparing the same list. + #[test] + fn every_check_is_reported_exactly_once_and_in_order() { + let (disclosure, view) = honest(); + let result = verify(&disclosure, &view); + let reported: Vec = result.verdicts().iter().map(|v| v.check).collect(); + assert_eq!(reported, Check::ALL.to_vec()); + } + + #[test] + fn a_disclosure_round_trips_through_its_file_form() { + let (disclosure, view) = honest(); + let text = serde_json::to_string(&disclosure).unwrap(); + let read: Disclosure = serde_json::from_str(&text).unwrap(); + assert!(verify(&read, &view).passed()); + } + + /// The action is copied out of the record rather than out of anything the + /// member typed, so what the disclosure claims is what the chain holds. + #[test] + fn the_claimed_action_is_taken_from_the_record() { + let (disclosure, _) = honest(); + assert_eq!(disclosure.action.beneficiary, beneficiary().to_string()); + assert_eq!(disclosure.action.selector, SELECTOR); + assert_eq!(disclosure.action.target_program, target().to_string()); + assert_eq!(disclosure.action.relay_fee, FEE); + assert_eq!(disclosure.action.payload, hex::encode(b"delegate")); + } + + // -- the tamper matrix -------------------------------------------------- + + /// Wrong `k`: everything derived from the nullifier preimage moves at once. + /// The fan-out is the point — `k` is what ties the leaf, the nullifier and + /// the record address together, so a substituted one cannot satisfy any of + /// them. + #[test] + fn a_disclosure_with_the_wrong_k_fails_every_check_that_derives_from_it() { + let result = verify_with(|d, _| d.k = hex::encode(Field::from_u64(42).to_bytes())); + assert_eq!( + failed(&result), + vec![ + Check::SecretsRecomputeToTheCommitment, + Check::TheCommitmentIsTheStatedLeaf, + Check::SecretsRecomputeToTheNullifier, + Check::TheRecordAddressIsTheNullifiersPda, + ] + ); + } + + /// Wrong `r`: the commitment moves and the nullifier does not, because `r` + /// blinds the leaf and never enters `H1`. + #[test] + fn a_disclosure_with_the_wrong_r_fails_the_commitment_but_not_the_nullifier() { + let result = verify_with(|d, _| d.r = hex::encode(Field::from_u64(99).to_bytes())); + assert_eq!( + failed(&result), + vec![ + Check::SecretsRecomputeToTheCommitment, + Check::TheCommitmentIsTheStatedLeaf, + ] + ); + } + + #[test] + fn a_non_canonical_secret_is_refused_rather_than_reduced() { + let result = verify_with(|d, _| d.k = hex::encode([0xffu8; 32])); + assert!(detail(&result, Check::SecretsRecomputeToTheCommitment).contains("canonical")); + } + + /// A leaf set that is internally consistent — its root matches the chain — + /// but does not contain this note. This is the shape a member pointing at + /// somebody else's pool would produce. + #[test] + fn a_commitment_absent_from_the_recovered_leaf_set_is_refused() { + let result = verify_with(|_, view| { + view.leaves[LEAF as usize] = Field::from_u64(31_337).to_bytes(); + view.root = root_of(&view.leaves); + }); + assert_eq!(failed(&result), vec![Check::TheCommitmentIsTheStatedLeaf]); + assert!(detail(&result, Check::TheCommitmentIsTheStatedLeaf).contains("not in the pool")); + } + + /// The right note, the wrong index. Nothing else in the file changes, and + /// only the check that compares the two notices. + #[test] + fn the_right_commitment_at_the_wrong_leaf_index_is_refused() { + let result = verify_with(|d, _| d.leaf_index = 1); + assert_eq!(failed(&result), vec![Check::TheCommitmentIsTheStatedLeaf]); + assert!(detail(&result, Check::TheCommitmentIsTheStatedLeaf) + .contains(&format!("commit to leaf {LEAF}"))); + } + + #[test] + fn a_leaf_index_beyond_the_recovered_history_is_refused() { + let result = verify_with(|d, _| d.leaf_index = 4_000); + assert_eq!(failed(&result), vec![Check::TheCommitmentIsTheStatedLeaf]); + assert!(detail(&result, Check::TheCommitmentIsTheStatedLeaf).contains("only 6 leaves")); + } + + /// An incomplete or reordered history. Every other check still passes, + /// which is exactly why this one has to exist: without it a verifier would + /// be trusting whoever served the leaves. + #[test] + fn a_rebuilt_root_that_disagrees_with_the_chain_is_refused() { + let result = verify_with(|_, view| view.root = [0xAB; 32]); + assert_eq!(failed(&result), vec![Check::TheRebuiltRootMatchesTheChain]); + } + + /// The file's own nullifier field is never believed. It is compared against + /// `H1(k)`, and the record address is derived from `H1(k)` regardless — so + /// this fails the equality check and nothing else. + #[test] + fn a_nullifier_field_inconsistent_with_the_secrets_is_refused() { + let result = verify_with(|d, _| d.nullifier = hex::encode([0x01u8; 32])); + assert_eq!(failed(&result), vec![Check::SecretsRecomputeToTheNullifier]); + } + + #[test] + fn a_spend_record_address_that_is_not_the_derived_pda_is_refused() { + let result = + verify_with(|d, _| d.spend_record = Pubkey::new_from_array([9u8; 32]).to_string()); + assert_eq!( + failed(&result), + vec![Check::TheRecordAddressIsTheNullifiersPda] + ); + } + + /// Fail-closed: the three checks that need a record fail on their own terms + /// rather than being skipped, because a skipped check reads as a passed one + /// to anybody scanning the table. + #[test] + fn an_absent_spend_record_fails_every_check_that_needs_one() { + let result = verify_with(|_, view| view.record = None); + assert_eq!( + failed(&result), + vec![ + Check::TheRecordExistsOnChain, + Check::TheRecordIsSettled, + Check::TheRecordBelongsToThisPool, + Check::TheRecordMatchesTheClaimedAction, + ] + ); + } + + #[test] + fn a_record_that_is_still_pending_is_refused() { + let result = verify_with(|_, view| { + view.record.as_mut().unwrap().settled = false; + }); + assert_eq!(failed(&result), vec![Check::TheRecordIsSettled]); + assert!(detail(&result, Check::TheRecordIsSettled).contains("pending")); + } + + /// A record from another denomination's pool. The address is pool-scoped, so + /// reaching this state means the record itself was tampered with, and the + /// check that reads its `pool` field is the one that catches it. + #[test] + fn a_record_belonging_to_a_different_pool_is_refused() { + let result = verify_with(|_, view| { + view.record.as_mut().unwrap().pool = [0x55; 32]; + }); + assert_eq!(failed(&result), vec![Check::TheRecordBelongsToThisPool]); + } + + #[test] + fn a_substituted_beneficiary_is_refused() { + let result = verify_with(|d, _| { + d.action.beneficiary = Pubkey::new_from_array([0x21; 32]).to_string(); + }); + assert_eq!( + failed(&result), + vec![Check::TheRecordMatchesTheClaimedAction] + ); + assert!(detail(&result, Check::TheRecordMatchesTheClaimedAction).contains("beneficiary")); + } + + #[test] + fn a_substituted_selector_is_refused() { + let result = verify_with(|d, _| d.action.selector = SELECTOR + 1); + assert!(detail(&result, Check::TheRecordMatchesTheClaimedAction).contains("selector")); + assert_eq!( + failed(&result), + vec![Check::TheRecordMatchesTheClaimedAction] + ); + } + + #[test] + fn a_substituted_target_program_is_refused() { + let result = verify_with(|d, _| { + d.action.target_program = Pubkey::new_from_array([0x31; 32]).to_string(); + }); + assert!(detail(&result, Check::TheRecordMatchesTheClaimedAction).contains("target program")); + assert_eq!( + failed(&result), + vec![Check::TheRecordMatchesTheClaimedAction] + ); + } + + #[test] + fn a_substituted_relay_fee_is_refused() { + let result = verify_with(|d, _| d.action.relay_fee = FEE + 1); + assert!(detail(&result, Check::TheRecordMatchesTheClaimedAction).contains("relay fee")); + assert_eq!( + failed(&result), + vec![Check::TheRecordMatchesTheClaimedAction] + ); + } + + /// The payload is the action's parameters. A disclosure that understated it + /// would describe a different instruction than the one the pool executed. + #[test] + fn a_substituted_payload_is_refused() { + let result = verify_with(|d, _| d.action.payload = hex::encode(b"withdraw")); + assert!(detail(&result, Check::TheRecordMatchesTheClaimedAction).contains("payload")); + assert_eq!( + failed(&result), + vec![Check::TheRecordMatchesTheClaimedAction] + ); + } + + /// The pool the disclosure names holds a different size than it claims, so + /// the commitment it recomputes belongs to another tree entirely. + #[test] + fn a_denomination_the_pool_does_not_hold_is_refused() { + let result = verify_with(|_, view| view.denomination = DENOM + 1); + assert_eq!(failed(&result), vec![Check::ThePoolMatchesTheDenomination]); + } + + /// The pool address is a PDA of the program and the denomination, so a + /// disclosure naming some other account is refused without an RPC call. + #[test] + fn a_pool_that_is_not_the_pda_for_the_denomination_is_refused() { + let result = verify_with(|d, _| d.pool = Pubkey::new_from_array([0x41; 32]).to_string()); + // The record's own pool field no longer matches either, which is the + // record correctly disagreeing with a substituted claim. + assert_eq!( + failed(&result), + vec![ + Check::ThePoolMatchesTheDenomination, + Check::TheRecordAddressIsTheNullifiersPda, + Check::TheRecordBelongsToThisPool, + ] + ); + } + + /// A file this tool cannot parse must not verify. It is the case where a + /// bare `is_err()` would be indistinguishable from a passing claim. + #[test] + fn a_malformed_field_fails_closed_rather_than_erroring() { + let result = verify_with(|d, _| d.commitment = "not hex".to_string()); + assert!(!result.passed()); + assert!(detail(&result, Check::SecretsRecomputeToTheCommitment).contains("not hex")); + } + + // -- building ----------------------------------------------------------- + + /// The refusal that protects the discloser. An unspent note's secrets are + /// the deposit, so writing them into a file to hand somebody is not a + /// disclosure — it is a transfer. + #[test] + fn a_disclosure_for_an_unspent_note_is_refused() { + let program = program(); + let (pool, _) = pool_address(&program, DENOM); + let stored = StoredNote::from_note(¬e(), DENOM).unwrap(); + let leaves = leaves_with(stored.commitment_bytes().unwrap()); + let view = ChainView { + denomination: DENOM, + k_floor: 2, + root: root_of(&leaves), + settled_spends: 5, + leaves, + record: None, + }; + let err = build(&program, &pool, &stored, &view, None) + .unwrap_err() + .to_string(); + assert!(err.contains("has not been spent"), "{err}"); + } + + #[test] + fn a_disclosure_for_a_pending_spend_is_refused() { + let program = program(); + let (pool, _) = pool_address(&program, DENOM); + let stored = StoredNote::from_note(¬e(), DENOM).unwrap(); + let leaves = leaves_with(stored.commitment_bytes().unwrap()); + let mut record = record_for(&pool); + record.settled = false; + let view = ChainView { + denomination: DENOM, + k_floor: 2, + root: root_of(&leaves), + settled_spends: 5, + leaves, + record: Some(record), + }; + let err = build(&program, &pool, &stored, &view, None) + .unwrap_err() + .to_string(); + assert!(err.contains("has not settled"), "{err}"); + } + + #[test] + fn a_disclosure_for_a_note_the_pool_never_held_is_refused() { + let program = program(); + let (pool, _) = pool_address(&program, DENOM); + let stored = StoredNote::from_note(¬e(), DENOM).unwrap(); + let leaves: Vec<[u8; 32]> = (1..=6u64).map(|i| Field::from_u64(i).to_bytes()).collect(); + let view = ChainView { + denomination: DENOM, + k_floor: 2, + root: root_of(&leaves), + settled_spends: 5, + leaves, + record: Some(record_for(&pool)), + }; + let err = build(&program, &pool, &stored, &view, None) + .unwrap_err() + .to_string(); + assert!(err.contains("not a leaf"), "{err}"); + } + + // -- the co-participant gate -------------------------------------------- + + #[test] + fn the_gate_counts_what_is_left_for_everybody_else() { + let cost = CoParticipantCost::new(5, 2); + assert_eq!(cost.remaining, 4); + assert!(cost.leaves_a_crowd()); + // Exactly at the floor is still a crowd; one below is not. + assert!(CoParticipantCost::new(3, 2).leaves_a_crowd()); + assert!(!CoParticipantCost::new(2, 2).leaves_a_crowd()); + } + + fn view_with_settled(settled: u64, k_floor: u32) -> (Pubkey, Pubkey, StoredNote, ChainView) { + let program = program(); + let (pool, _) = pool_address(&program, DENOM); + let stored = StoredNote::from_note(¬e(), DENOM).unwrap(); + let leaves = leaves_with(stored.commitment_bytes().unwrap()); + let view = ChainView { + denomination: DENOM, + k_floor, + root: root_of(&leaves), + settled_spends: settled, + leaves, + record: Some(record_for(&pool)), + }; + (program, pool, stored, view) + } + + /// Disclosing is only free to the person doing it. Below the floor the + /// default is refusal, and the message names the people it would cost. + #[test] + fn the_gate_refuses_a_disclosure_that_leaves_the_others_below_the_floor() { + let (program, pool, stored, view) = view_with_settled(2, 2); + let err = build(&program, &pool, &stored, &view, None) + .unwrap_err() + .to_string(); + assert!(err.contains("REFUSING"), "{err}"); + assert!(err.contains(ACKNOWLEDGE_FLAG), "{err}"); + } + + #[test] + fn the_gate_permits_a_disclosure_that_leaves_a_crowd() { + let (program, pool, stored, view) = view_with_settled(3, 2); + assert!(build(&program, &pool, &stored, &view, None).is_ok()); + } + + /// The override is a value the caller has to construct out of the cost it is + /// overriding, so it cannot be produced without the number being computed. + #[test] + fn an_explicit_override_permits_a_disclosure_below_the_floor() { + let (program, pool, stored, view) = view_with_settled(2, 2); + let consent = DisclosureOverride::acknowledging(CoParticipantCost::new( + view.settled_spends, + view.k_floor, + )); + let disclosure = build(&program, &pool, &stored, &view, Some(&consent)).unwrap(); + assert!(verify(&disclosure, &view).passed()); + assert!(!consent.cost().leaves_a_crowd()); + } +} diff --git a/crates/mirror-cli/src/lookup.rs b/crates/mirror-cli/src/lookup.rs new file mode 100644 index 00000000..2e5cc13d --- /dev/null +++ b/crates/mirror-cli/src/lookup.rs @@ -0,0 +1,346 @@ +//! Address lookup tables, as much of them as a settlement needs. +//! +//! A legacy transaction names every account it touches by its full 32 bytes, so +//! a batch of members costs about 99 bytes each and the 1232-byte packet stops +//! it at ten. A v0 transaction may instead name accounts by a one-byte index +//! into a table published on chain, which drops the marginal cost of a member by +//! more than an order of magnitude and moves the ceiling somewhere else +//! entirely. `batch_ceiling.rs` measures the legacy number; this module is how +//! settlement stops being bound by it. +//! +//! The instruction layouts belong to somebody else's program and are written out +//! here rather than pulled in as a dependency, for the same reason `stake.rs` +//! does it: the crate that owns them drags in half the SDK, and a byte layout +//! whose derivation is written down can be checked by a reader. +//! +//! **A table is not free and it is not private.** It is a rent-paying account +//! that lists, publicly and durably, every address the settlement will touch — +//! published *before* the settlement lands. That is a real disclosure, and the +//! answer to it is `deactivate` + `close`: the rent comes back and the list +//! stops existing. A design that creates one of these per batch and never closes +//! it leaves both the lamports and the list behind, permanently, once per round. + +use anyhow::{anyhow, Result}; +use solana_program::{ + instruction::{AccountMeta, Instruction}, + pubkey::Pubkey, +}; + +pub const PROGRAM: &str = "AddressLookupTab1e1111111111111111111111111"; + +/// How many addresses one `extend` can carry. +/// +/// Bounded by the packet, not by the program: the instruction carries the +/// addresses in full, so 30 of them is 960 bytes and anything much larger stops +/// fitting. Extending in chunks is the normal way to fill a table. +pub const ADDRESSES_PER_EXTEND: usize = 20; + +/// Slots a table must sit deactivated before it can be closed. +/// +/// The cooldown exists because a transaction already in flight may still resolve +/// against the table, and closing it underneath such a transaction would change +/// what that transaction means. It is why reclaiming the rent is a later errand +/// rather than part of settling. +pub const DEACTIVATION_COOLDOWN_SLOTS: u64 = 513; + +fn program_id() -> Result { + PROGRAM.parse().map_err(|e| anyhow!("{PROGRAM}: {e}")) +} + +/// The address a table gets, given who owns it and which slot it was derived +/// against. +/// +/// The recent slot is part of the seed, which is what stops one authority from +/// creating the same table twice and what ties a table to a moment. The runtime +/// checks the slot is recent, so a stale one is refused rather than silently +/// producing a different address. +pub fn address(authority: &Pubkey, recent_slot: u64) -> Result<(Pubkey, u8)> { + Ok(Pubkey::find_program_address( + &[authority.as_ref(), &recent_slot.to_le_bytes()], + &program_id()?, + )) +} + +/// `CreateLookupTable { recent_slot, bump }` — discriminant 0. +pub fn create( + authority: &Pubkey, + payer: &Pubkey, + recent_slot: u64, +) -> Result<(Instruction, Pubkey)> { + let (table, bump) = address(authority, recent_slot)?; + let mut data = 0u32.to_le_bytes().to_vec(); + data.extend_from_slice(&recent_slot.to_le_bytes()); + data.push(bump); + Ok(( + Instruction::new_with_bytes( + program_id()?, + &data, + vec![ + AccountMeta::new(table, false), + AccountMeta::new_readonly(*authority, true), + AccountMeta::new(*payer, true), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ), + table, + )) +} + +/// `ExtendLookupTable { new_addresses }` — discriminant 2, then a bincode +/// sequence: a u64 length followed by the addresses. +pub fn extend( + table: &Pubkey, + authority: &Pubkey, + payer: &Pubkey, + addresses: &[Pubkey], +) -> Result { + let mut data = 2u32.to_le_bytes().to_vec(); + data.extend_from_slice(&(addresses.len() as u64).to_le_bytes()); + for a in addresses { + data.extend_from_slice(&a.to_bytes()); + } + Ok(Instruction::new_with_bytes( + program_id()?, + &data, + vec![ + AccountMeta::new(*table, false), + AccountMeta::new_readonly(*authority, true), + AccountMeta::new(*payer, true), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + )) +} + +/// `DeactivateLookupTable` — discriminant 3. Starts the cooldown; the table is +/// still usable until it elapses. +pub fn deactivate(table: &Pubkey, authority: &Pubkey) -> Result { + Ok(Instruction::new_with_bytes( + program_id()?, + &3u32.to_le_bytes(), + vec![ + AccountMeta::new(*table, false), + AccountMeta::new_readonly(*authority, true), + ], + )) +} + +/// `CloseLookupTable` — discriminant 4. Returns the rent and removes the +/// published address list. +pub fn close(table: &Pubkey, authority: &Pubkey, recipient: &Pubkey) -> Result { + Ok(Instruction::new_with_bytes( + program_id()?, + &4u32.to_le_bytes(), + vec![ + AccountMeta::new(*table, false), + AccountMeta::new_readonly(*authority, true), + AccountMeta::new(*recipient, false), + ], + )) +} + +/// The addresses a table must hold for `metas` to resolve, in first-seen order, +/// excluding those that cannot come from a table. +/// +/// Two kinds of account must stay in the transaction's static keys and so must +/// never enter the table. A **signer** cannot be served from a lookup table at +/// all — the runtime resolves the table after checking signatures. And the +/// **program being invoked** must be a static key, because the instruction +/// names it by an index into the static portion. +/// +/// Getting either wrong produces a transaction that compiles and is then +/// rejected by the cluster, so the filtering happens here, once, rather than at +/// each call site. +pub fn addresses_for(metas: &[AccountMeta], program: &Pubkey) -> Vec { + let mut out: Vec = Vec::new(); + for m in metas { + if m.is_signer || m.pubkey == *program || out.contains(&m.pubkey) { + continue; + } + out.push(m.pubkey); + } + out +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn create_carries_the_slot_and_the_bump_the_address_was_derived_from() { + let authority = Pubkey::new_unique(); + let slot = 305_419_896u64; + let (ix, table) = create(&authority, &authority, slot).unwrap(); + let (expected, bump) = address(&authority, slot).unwrap(); + assert_eq!(table, expected); + assert_eq!(&ix.data[..4], 0u32.to_le_bytes()); + assert_eq!(&ix.data[4..12], slot.to_le_bytes()); + assert_eq!(ix.data[12], bump, "the bump must be the derived one"); + assert_eq!(ix.data.len(), 13); + } + + /// The length prefix is a bincode `u64`, not a compact-u16. A four-byte + /// prefix would leave the program reading addresses from the wrong offset + /// and rejecting a table that was built correctly. + #[test] + fn extend_prefixes_the_addresses_with_a_u64_count() { + let table = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let addresses: Vec = (0..3).map(|_| Pubkey::new_unique()).collect(); + let ix = extend(&table, &authority, &authority, &addresses).unwrap(); + assert_eq!(&ix.data[..4], 2u32.to_le_bytes()); + assert_eq!(&ix.data[4..12], 3u64.to_le_bytes()); + assert_eq!(ix.data.len(), 12 + 3 * 32); + for (i, a) in addresses.iter().enumerate() { + let at = 12 + i * 32; + assert_eq!(&ix.data[at..at + 32], a.to_bytes().as_slice()); + } + } + + #[test] + fn deactivate_and_close_are_bare_discriminants() { + let table = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + assert_eq!( + deactivate(&table, &authority).unwrap().data, + 3u32.to_le_bytes() + ); + assert_eq!( + close(&table, &authority, &authority).unwrap().data, + 4u32.to_le_bytes() + ); + } + + /// The rent goes back to whoever is named, which is the whole point of + /// closing rather than abandoning. + #[test] + fn close_names_the_account_the_rent_returns_to() { + let table = Pubkey::new_unique(); + let authority = Pubkey::new_unique(); + let recipient = Pubkey::new_unique(); + let ix = close(&table, &authority, &recipient).unwrap(); + assert_eq!(ix.accounts[2].pubkey, recipient); + assert!(ix.accounts[2].is_writable); + } + + /// A signer served from a lookup table makes a transaction the cluster + /// refuses. So does the program being invoked. Both are filtered, and this + /// is the test that says so. + #[test] + fn neither_a_signer_nor_the_invoked_program_enters_the_table() { + let program = Pubkey::new_unique(); + let settler = Pubkey::new_unique(); + let pool = Pubkey::new_unique(); + let metas = vec![ + AccountMeta::new(settler, true), + AccountMeta::new(pool, false), + AccountMeta::new_readonly(program, false), + ]; + let addresses = addresses_for(&metas, &program); + assert_eq!(addresses, vec![pool]); + } + + /// What a table is actually worth, measured rather than argued. + /// + /// This builds settlement-shaped instructions — a settler, a pool, a vault, + /// then three accounts nobody else shares per member — and serializes them + /// both ways. The legacy figure must reproduce what `batch_ceiling.rs` + /// settles against the real program, which is what ties this arithmetic to + /// an executed measurement; the v0 figure is the point. + /// + /// The assertion is deliberately not "the ceiling is N". Once accounts cost + /// a byte each, the packet stops being the thing in the way at any batch + /// size worth settling, and what takes over — compute, the account-lock + /// limit, the program's own heap — depends on what the members are doing. + /// Pinning a number here would be pinning the wrong constraint. + #[test] + fn a_lookup_table_takes_the_packet_out_of_the_way() { + use solana_message::{v0, AddressLookupTableAccount, VersionedMessage}; + + const PACKET_DATA_SIZE: usize = 1280 - 40 - 8; + + // `batch_ceiling.rs` settles ten of these against the real program at + // 1228 bytes. If this model disagrees there, it is modelling something + // else and the v0 number below means nothing. + let legacy_at = |n: usize| -> usize { + let (settler, program) = (Pubkey::new_unique(), Pubkey::new_unique()); + let ix = settle_shaped(&settler, &program, n); + let msg = solana_message::Message::new(&[ix], Some(&settler)); + 1 + 64 + msg.serialize().len() + }; + assert_eq!( + legacy_at(10), + 1228, + "the legacy model no longer agrees with the executed measurement in \ + batch_ceiling.rs, so nothing else here can be trusted" + ); + assert!( + legacy_at(11) > PACKET_DATA_SIZE, + "eleven members are supposed to be over the packet" + ); + + let versioned_at = |n: usize| -> usize { + let (settler, program) = (Pubkey::new_unique(), Pubkey::new_unique()); + let ix = settle_shaped(&settler, &program, n); + let alt = AddressLookupTableAccount { + key: Pubkey::new_unique(), + addresses: addresses_for(&ix.accounts, &program), + }; + let msg = v0::Message::try_compile( + &settler, + &[ix], + &[alt], + solana_program::hash::Hash::default(), + ) + .expect("a settlement must compile against its own table"); + 1 + 64 + + bincode::serialize(&VersionedMessage::V0(msg)) + .expect("serializes") + .len() + }; + + // Sixty members is six times what the packet allows without a table, and + // it is not close to the limit — which is the whole finding. Ten is the + // floor a settler gets with no setup at all, not the most the program + // can carry. + let sixty = versioned_at(60); + assert!( + sixty < PACKET_DATA_SIZE, + "sixty members through a table weigh {sixty} bytes, over the packet" + ); + assert!( + versioned_at(10) < legacy_at(10) / 2, + "a table is supposed to cost far less per member than naming keys in full" + ); + } + + /// A settlement's account shape: three fixed, then three per member that no + /// other member shares. The same shape `batch_ceiling.rs` settles. + fn settle_shaped(settler: &Pubkey, program: &Pubkey, n: usize) -> Instruction { + let mut metas = vec![ + AccountMeta::new(*settler, true), + AccountMeta::new(Pubkey::new_unique(), false), + AccountMeta::new(Pubkey::new_unique(), false), + ]; + for _ in 0..n { + for _ in 0..3 { + metas.push(AccountMeta::new(Pubkey::new_unique(), false)); + } + } + Instruction::new_with_bytes(*program, &[4u8, n as u8], metas) + } + + /// A batch that names the same relay twice must not pay for it twice: the + /// table holds distinct addresses, and the saving is per distinct key. + #[test] + fn a_repeated_account_is_listed_once() { + let program = Pubkey::new_unique(); + let shared = Pubkey::new_unique(); + let other = Pubkey::new_unique(); + let metas = vec![ + AccountMeta::new(shared, false), + AccountMeta::new(other, false), + AccountMeta::new(shared, false), + ]; + assert_eq!(addresses_for(&metas, &program), vec![shared, other]); + } +} diff --git a/crates/mirror-cli/src/main.rs b/crates/mirror-cli/src/main.rs index 01bf46a2..ad654ae1 100644 --- a/crates/mirror-cli/src/main.rs +++ b/crates/mirror-cli/src/main.rs @@ -8,7 +8,9 @@ use std::path::PathBuf; mod chain; mod client; mod crowd; +mod disclose; mod history; +mod lookup; mod note; mod soak; mod stake; @@ -218,6 +220,56 @@ enum Command { #[arg(long)] render_only: bool, }, + /// Proves to a verifier of your choosing that a settled action was yours. + /// + /// Nothing on chain changes and nobody else learns anything: a disclosure is + /// a file handed to one counterparty, not a key anybody holds. It carries + /// the note's secrets, which authorise nothing once the nullifier is burnt — + /// and which would hand over the deposit if the note were still unspent, + /// which is why this refuses to write one for a note that has not settled. + Disclose { + #[arg(long)] + program: String, + #[arg(long)] + note: PathBuf, + /// Where to write the disclosure. Overwrites: unlike a note, it can be + /// regenerated from the note at any time. + #[arg(long, default_value = "disclosure.json")] + out: PathBuf, + /// Disclose even though doing so leaves the pool's remaining settled + /// actions below its floor. The cover that costs is the other members', + /// and they are neither asked nor told. + #[arg(long)] + i_accept_the_cost_to_others: bool, + #[arg(long, default_value = DEFAULT_URL)] + url: String, + }, + /// Checks a disclosure against the chain, recomputing every value in it. + /// + /// Nothing in the file is taken on trust. The commitment and the nullifier + /// are rederived from the secrets, the spend record's address is derived + /// from that nullifier, the accumulator is rebuilt from history and checked + /// against the pool's own root, and the action is read out of the record. + /// Every check is reported separately, and any check that cannot be + /// completed is a failure rather than a silence. + DiscloseVerify { + #[arg(long)] + file: PathBuf, + #[arg(long, default_value = DEFAULT_URL)] + url: String, + }, + /// Reclaims a settlement's lookup table once its cooldown has passed. + /// + /// A large settlement publishes a table naming every account it touches. + /// This returns the rent and removes that list. + CloseTable { + #[arg(long)] + table: String, + #[arg(long, default_value = "https://api.devnet.solana.com")] + url: String, + #[arg(long, default_value = "~/.config/solana/id.json")] + keypair: String, + }, /// Recomputes the verifying key from a seed and reports its digest. /// /// This is the check a third party runs. It binds the *whole* key — alpha, @@ -983,6 +1035,37 @@ fn main() -> Result<()> { keypair, out, } => soak::run(&program, &url, &keypair, &out), + Command::Disclose { + program, + note, + out, + i_accept_the_cost_to_others, + url, + } => { + let chain = chain::Chain::new(&url); + disclose::create( + &chain, + &parse_program(&program)?, + ¬e, + &out, + i_accept_the_cost_to_others, + ) + } + Command::DiscloseVerify { file, url } => { + let chain = chain::Chain::new(&url); + disclose::check(&chain, &file) + } + Command::CloseTable { + table, + url, + keypair, + } => { + let authority = soak::read_keypair(&keypair)?; + let table: solana_program::pubkey::Pubkey = table + .parse() + .map_err(|e| anyhow::anyhow!("bad table address: {e}"))?; + client::close_table(&chain::Chain::new(&url), &table, &authority) + } Command::Crowd { program, url, diff --git a/crates/mirror-core/Cargo.toml b/crates/mirror-core/Cargo.toml index 10805ac5..213f467a 100644 --- a/crates/mirror-core/Cargo.toml +++ b/crates/mirror-core/Cargo.toml @@ -2,6 +2,9 @@ name = "mirror-core" version.workspace = true edition.workspace = true +# Not published to crates.io: these crates are built from this repository, and a +# registry copy would be a second source of truth for the same code. +publish = false license.workspace = true # No arkworks here. mirror-core is linked into the on-chain program, and every diff --git a/crates/mirror-provenance/Cargo.toml b/crates/mirror-provenance/Cargo.toml index aadf7517..5b8be849 100644 --- a/crates/mirror-provenance/Cargo.toml +++ b/crates/mirror-provenance/Cargo.toml @@ -2,6 +2,9 @@ name = "mirror-provenance" version.workspace = true edition.workspace = true +# Not published to crates.io: these crates are built from this repository, and a +# registry copy would be a second source of truth for the same code. +publish = false license.workspace = true [dependencies] diff --git a/deny.toml b/deny.toml new file mode 100644 index 00000000..adbe4e97 --- /dev/null +++ b/deny.toml @@ -0,0 +1,81 @@ +# Supply-chain policy, enforced in CI by `cargo deny check`. +# +# A privacy protocol's dependency tree is part of its threat surface: a +# compromised transitive crate in the proving path sees the witness, and one in +# the client sees the note. The point of this file is not to reach a green tick +# — it is that every exception below has to be written down with a reason +# somebody can argue with. + +[advisories] +version = 2 +# No blanket ignores. Every entry below is an *unmaintained* notice — not a +# vulnerability — and each carries the reason it does not reach anything this +# protocol depends on. An advisory with a security impact gets fixed, not listed. +ignore = [ + # ansi_term — terminal colour codes, reached only through the CLI's argument + # parser. It touches no key, no note and no proof, and its whole surface is + # writing escape sequences to a terminal this project controls. + { id = "RUSTSEC-2021-0139", reason = "unmaintained; terminal colouring in the CLI, not on any cryptographic or custody path" }, + + # derivative — a proc-macro arkworks uses to derive traits. It runs at + # compile time and emits no code of its own into the binary, so + # "unmaintained" here means the macro will not gain features, not that + # anything ships. + { id = "RUSTSEC-2024-0388", reason = "unmaintained; compile-time proc-macro from arkworks, contributes no runtime code" }, + + # paste — the same situation: a compile-time token-pasting macro pulled in + # transitively. Nothing of it exists at runtime. + { id = "RUSTSEC-2024-0436", reason = "unmaintained; compile-time proc-macro, contributes no runtime code" }, + + # bincode 1.3 — this one *is* on the path, and is listed rather than waved + # away: it serializes transactions in `chain.rs`. It stays because the wire + # format it implements is fixed by the Solana runtime and cannot change + # without the cluster changing, and because every crate in the ecosystem that + # builds a transaction depends on the same version. "Unmaintained" for a + # frozen format is a statement about the crate's future, not its behaviour. + { id = "RUSTSEC-2025-0141", reason = "unmaintained; implements a wire format frozen by the Solana runtime, and is the version the whole ecosystem pins" }, + + # libsecp256k1 — arrives through `solana-program`'s secp256k1 recovery + # support. This program verifies Groth16 over BN254 and never calls a + # secp256k1 entry point; `grep` for one across the workspace finds nothing. + { id = "RUSTSEC-2025-0161", reason = "unmaintained; transitive through solana-program's secp256k1 support, which this workspace never calls" }, +] + +[licenses] +version = 2 +# Permissive only. This repository is MIT and a copyleft dependency anywhere in +# the tree would make that claim false. +allow = [ + "MIT", + "Apache-2.0", + "Apache-2.0 WITH LLVM-exception", + "BSD-2-Clause", + "BSD-3-Clause", + "ISC", + "Unicode-3.0", + "Zlib", + "CC0-1.0", + "MPL-2.0", + # The Mozilla CA root store, carried by `webpki-roots`. CDLA-Permissive-2.0 + # is a *data* licence — it governs the certificate list, not code, imposes no + # copyleft and no attribution requirement on downstream use. It is here + # because a TLS client needs roots from somewhere, and taking them from + # Mozilla's published store is better than pinning our own. + "CDLA-Permissive-2.0", +] +confidence-threshold = 0.9 + +[bans] +multiple-versions = "warn" +wildcards = "deny" +# The wildcards this would otherwise reject are the workspace's own crates +# depending on each other by path. They are in this repository, they are built +# from this commit, and there is no registry version for them to drift to. +allow-wildcard-paths = true + +[sources] +unknown-registry = "deny" +unknown-git = "deny" +# Every dependency comes from crates.io. A git dependency is a version nobody +# else can resolve and an audit nobody else can repeat. +allow-registry = ["https://github.com/rust-lang/crates.io-index"] diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 05d9101f..442c69c1 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -14,8 +14,9 @@ mirror-core field, Poseidon, Merkle accumulator, notes (linked on-chain mirror-circuit R1CS gadget, membership circuit, prover (host only) mirror-pool the on-chain program mirror-provenance funding-provenance measurement (host only) -mirror-cli members: init-pool, note-new, deposit, tree, spend, settle - operators: setup, verify-setup, soak, crowd +mirror-cli members: init-pool, note-new, deposit, tree, spend, settle, + disclose, disclose-verify + operators: setup, verify-setup, soak, crowd, close-table measuring: check-endpoint, seeds, collect, analyze, compare, selection ``` diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 57724240..500ec73c 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -173,12 +173,44 @@ Binding an account-list commitment into the proof would close this. It is not implemented, and the claim elsewhere that "a relay cannot redirect an action" is about the selector, target, fee and payload, not about the account list. -### Timing at submission - -Settlement batches payouts, but `submit_spend` is a transaction at a time of the -relay's choosing. A relay that submits immediately on request leaks the member's -timing. This is relay policy, not a protocol guarantee, and we do not claim -otherwise. +### The submission phase is public, and it is the weaker half + +Settlement is one transaction, one signer and one timestamp, and that is the +property the design is built around. It is also only half of what an observer +sees, and the other half deserves stating plainly rather than being left implicit +in a claim carefully scoped to the settled transaction. + +`submit_spend` is **one transaction per member**, at a moment of the relay's +choosing, and it publishes in cleartext the beneficiary, the selector, the target +program, the payload and the fee — everything about the action except who asked +for it. Three things follow, and all three are real: + +- **Timing.** A relay that submits immediately on request leaks when the member + asked. This is relay policy, not a protocol guarantee. +- **Ordering.** Settlement executes the batch in the order the settler passes the + records, and nothing shuffles them. A settler who preserves submission order + makes position in the settled batch a restatement of submission order, and + arrival order is public. +- **The signer.** The relay signs, so `accountKeys[0]` of a `submit_spend` is the + relay. If a member relays for themselves — which the protocol permits, and + which `USAGE.md` documents as the escape hatch — that transaction names them + beside the action they are about to take, and the settlement's anonymity is + worth nothing to them. + +The last one is the sharpest, because it is not a subtle statistical channel: it +is one public transaction that ends the question. **The tool refuses the version +of this mistake it can detect** — a relay key that has also deposited into the +pool — and cannot detect the rest, because nothing on chain distinguishes a +member's own fresh key from a genuine third-party relay. Funding is where it +usually goes wrong: a relay topped up from the depositing wallet leads back in +one hop. + +So the honest statement of what the pool provides is narrower than "one signer, +therefore anonymous". It is: **given that the member never signs and never funds +their own relay, the settled action cannot be attributed to them.** The first +clause is a discipline the member keeps, not a property the program enforces, and +a threat model that omits it is describing a smaller adversary than the one that +exists. ### Amounts are public diff --git a/docs/USAGE.md b/docs/USAGE.md index 19b9d4f2..a2c2e080 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -220,6 +220,62 @@ transaction, at one timestamp. and reported, because the record binds *how many* accounts the call takes and never *which*, so no tool can infer the account list its callee expects. +## 7. Proving it was you — to one person, later, if you want to + +Anonymity that cannot be given up on purpose is a liability. At some point a +member may need to show an exchange, an accountant or a counterparty that a +particular action was theirs — and the usual answer to that is a viewing key or +an auditor, which means a standing capability somebody else holds. + +There is none here. A disclosure is a **file you hand to one person you chose**: + +``` +mirror disclose --program --note m1.json --out disclosure.json +``` + +Give them the file. They check it against the chain, and nothing in it is taken +on trust — the commitment and the nullifier are recomputed from your secrets, the +record's address is derived from that recomputed nullifier, the accumulator is +rebuilt from history and checked against the pool's own root, and the action is +read out of the record: + +``` +mirror disclose-verify --file disclosure.json +``` + +Every check is reported separately and a check that cannot be completed is a +failure, never a silence. A file whose stated nullifier disagrees with what your +secrets produce fails on that check while the rest still pass, which tells the +verifier exactly what was tampered with. + +**It only works after settlement, and the tool refuses before it.** The disclosure +carries the note's secrets, and before the note is spent those secrets *are* the +money — anyone holding them can prove membership and send the payout wherever +they like. After the nullifier is burnt they authorise nothing, and all that is +left in them is the ability to demonstrate the link. That is the whole difference +between a disclosure and handing over a deposit. + +**Disclosing costs the other members, and the tool says so before you do it.** +Proving one action was yours removes you as a candidate for every other action in +the pool. If that would leave the remaining set below the pool's floor, the +command refuses and tells you what it would cost: + +``` +Error: REFUSING: this pool has settled 6 spend(s) and its floor is 6. Naming one +of them as yours leaves 5 unattributed, which is below the floor. + +The people that costs are not you. Every settled action is a candidate for every +member; removing one narrows the guess for all the rest, and they did not agree +to it and will not be told. + +If you have weighed that and still want to, pass --i-accept-the-cost-to-others. +Nothing here can stop you disclosing out-of-band anyway — the secrets are yours. +This exists so the cost is visible at the moment it is paid. +``` + +The gate is advisory by construction, and the message says so rather than +pretending otherwise. + ## How many members settle together It depends on what the members are doing: diff --git a/programs/mirror-pool/Cargo.toml b/programs/mirror-pool/Cargo.toml index 304dc2ac..a5997ae4 100644 --- a/programs/mirror-pool/Cargo.toml +++ b/programs/mirror-pool/Cargo.toml @@ -2,6 +2,9 @@ name = "mirror-pool-program" version.workspace = true edition.workspace = true +# Not published to crates.io: these crates are built from this repository, and a +# registry copy would be a second source of truth for the same code. +publish = false license.workspace = true [lib] @@ -24,6 +27,8 @@ solana-signer.workspace = true solana-transaction.workspace = true solana-message.workspace = true ark-std.workspace = true +sha2.workspace = true +hex.workspace = true solana-system-interface.workspace = true # The entrypoint! macro emits these; declaring them keeps `-D warnings` honest diff --git a/programs/mirror-pool/tests/batch_ceiling.rs b/programs/mirror-pool/tests/batch_ceiling.rs index bb147e3e..d3a97927 100644 --- a/programs/mirror-pool/tests/batch_ceiling.rs +++ b/programs/mirror-pool/tests/batch_ceiling.rs @@ -12,6 +12,16 @@ //! a smaller batch: the ceiling is found by settling real spends against the real //! `.so` in litesvm, and both limits are reported at the answer. //! +//! **This measures a *legacy* transaction, and that scoping is the whole +//! meaning of the number.** A legacy message names every account by its full 32 +//! bytes, which is what makes a member cost ~99 bytes and what puts the wall at +//! ten. A v0 message may instead resolve accounts through an address lookup +//! table published on chain, at one byte per account — the packet then stops +//! binding entirely, and something else takes over. `crates/mirror-cli/src/ +//! lookup.rs` is that path, and `mirror settle` takes it automatically for any +//! batch this test would refuse. Ten is the floor a settler gets with no setup +//! at all, not the most the program can do. +//! //! Requires `make build-sbf` first, like the end-to-end suite. This is a //! measurement of the deployed artefact, not of the host crate, so a missing //! artefact says so rather than quietly measuring nothing. diff --git a/programs/mirror-pool/tests/vk_drift.rs b/programs/mirror-pool/tests/vk_drift.rs new file mode 100644 index 00000000..fe5e698f --- /dev/null +++ b/programs/mirror-pool/tests/vk_drift.rs @@ -0,0 +1,97 @@ +//! The verifying key compiled into the program is the one the published seed +//! produces — checked here, not by an operator remembering to run a command. +//! +//! `README.md` publishes a digest and tells a reader they can re-derive the key +//! from the committed seed and compare. That claim is only worth anything if +//! something enforces it: `vk.rs` is a generated file of plain byte arrays, and +//! a wrong byte in it is not a compile error, not a test failure anywhere else, +//! and not visible in a diff anyone reads carefully. It is a silent break of the +//! one property this setup does offer — that it is *reproducible*, since it is +//! openly not *secure*. +//! +//! What a drift here would mean is worse than a broken build. The program would +//! keep verifying proofs, against a key nobody can derive, and every claim about +//! reproducibility in this repository would be false while every test stayed +//! green. +//! +//! So this compares element by element rather than by digest alone. A digest +//! tells you *that* something moved; the element comparison tells you *which* +//! part of the key did, which is the difference between a five-minute fix and an +//! afternoon. + +use mirror_circuit::generate_reproducible; +use mirror_pool_program::vk; + +/// The same seed `mirror setup` and `mirror verify-setup` use. Committed in +/// plain sight, because the whole point is that anyone can run the setup again. +const SEED: &[u8] = b"mirror-pool-reproducible-dev-setup-v1"; + +/// The digest `README.md` publishes. +/// +/// Pinned here so that changing the number in the documentation without changing +/// the key — or the reverse — fails rather than passes. The preimage is the +/// concatenation of every element in the order below, which is what makes this +/// a statement about the whole key rather than about one point of it. +const PUBLISHED_DIGEST: &str = "b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7"; + +#[test] +fn the_compiled_verifying_key_is_the_one_the_published_seed_derives() { + let derived = generate_reproducible(SEED) + .expect("the committed seed must produce a setup") + .solana_vk(); + + assert_eq!( + derived.alpha_g1, + vk::VK_ALPHA_G1, + "alpha_g1 in the program does not match the seed" + ); + assert_eq!( + derived.beta_g2, + vk::VK_BETA_G2, + "beta_g2 in the program does not match the seed" + ); + assert_eq!( + derived.gamma_g2, + vk::VK_GAMMA_G2, + "gamma_g2 in the program does not match the seed" + ); + assert_eq!( + derived.delta_g2, + vk::VK_DELTA_G2, + "delta_g2 in the program does not match the seed" + ); + + // The IC points are per public input, so a mismatch in their *count* means + // the circuit's shape changed and the program is verifying a different + // statement than the one this repository documents. + assert_eq!( + derived.ic.len(), + vk::VK_IC.len(), + "the program expects {} public inputs and the circuit produces {}", + vk::VK_IC.len(), + derived.ic.len() + ); + for (i, point) in derived.ic.iter().enumerate() { + assert_eq!(*point, vk::VK_IC[i], "IC point {i} does not match the seed"); + } +} + +/// The number in the README, enforced. +/// +/// Deliberately a separate test from the element comparison above. If both fail, +/// the key moved; if only this one fails, the documentation is stale and the key +/// is fine — and knowing which without reading any code is the point. +#[test] +fn the_published_digest_is_the_digest_of_the_derived_key() { + use sha2::{Digest, Sha256}; + let derived = generate_reproducible(SEED) + .expect("the committed seed must produce a setup") + .solana_vk(); + let mut hasher = Sha256::new(); + hasher.update(derived.digest_preimage()); + let digest = hex::encode(hasher.finalize()); + assert_eq!( + digest, PUBLISHED_DIGEST, + "the digest published in README.md is not the digest of the key this seed derives" + ); +} From 0077af8f5800741d21b3e351671467a8067a6d28 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 04:36:57 -0300 Subject: [PATCH 69/83] fix: a member could not spend their own note against a busy pool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rebuilding a pool's accumulator costs one getTransaction per signature, and that is the method public endpoints throttle hardest. The client sent the whole burst back to back and gave up after seven attempts, so on a pool with real history `mirror spend` failed with a rate-limit error at the one moment a member needs it — trying to spend a note they own. Two changes, both about the same failure. The rebuild now pauses briefly between transaction fetches, which costs seconds and removes the burst. And the retry budget goes to ten attempts with the backoff capped at thirty seconds, so a client waits about four minutes before concluding an endpoint will not serve it, rather than about one. Found by running the tool against a pool with two dozen deposits, which is the first time the scan was long enough to matter. --- crates/mirror-cli/src/chain.rs | 15 +++++++++++++-- crates/mirror-cli/src/history.rs | 9 +++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/crates/mirror-cli/src/chain.rs b/crates/mirror-cli/src/chain.rs index 983bb0f6..f673c62b 100644 --- a/crates/mirror-cli/src/chain.rs +++ b/crates/mirror-cli/src/chain.rs @@ -38,10 +38,18 @@ impl Chain { let body = serde_json::json!({ "jsonrpc": "2.0", "id": 1, "method": method, "params": params, }); + // Ten attempts with a bounded exponential backoff, which is roughly + // four minutes of patience before giving up. Seven was not enough, and + // the reason is specific rather than a matter of taste: rebuilding a + // pool's accumulator means one `getTransaction` per signature, and the + // public endpoints throttle that method far harder than they throttle a + // balance read. A client that gives up during the burst leaves a member + // unable to spend a note they own, which is the one failure this tool + // must not have. let mut wait = std::time::Duration::from_millis(500); let mut last: Option = None; let mut response: Option = None; - for _ in 0..7 { + for _ in 0..10 { match self.agent.post(&self.endpoint).send_json(body.clone()) { Ok(r) => { response = Some(r.into_json()?); @@ -49,7 +57,10 @@ impl Chain { } Err(ureq::Error::Status(429, _)) => { std::thread::sleep(wait); - wait *= 2; + // Capped, because doubling ten times is over eight minutes + // of sleeping for the last attempt alone, and a member + // watching a command hang learns nothing from it. + wait = (wait * 2).min(std::time::Duration::from_secs(30)); last = Some("rate limited".into()); } Err(e) => return Err(anyhow!("{method}: {e}")), diff --git a/crates/mirror-cli/src/history.rs b/crates/mirror-cli/src/history.rs index 4e548ba6..496a51cb 100644 --- a/crates/mirror-cli/src/history.rs +++ b/crates/mirror-cli/src/history.rs @@ -86,6 +86,15 @@ pub fn scan(chain: &Chain, program_id: &Pubkey, pool: &Pubkey, verbose: bool) -> if verbose && n > 0 && n % 25 == 0 { println!(" {n}/{} …", signatures.len()); } + // Paced deliberately. `getTransaction` is the expensive RPC method and + // the one public endpoints throttle first, and a rebuild asks for it + // once per signature — so a pool with any history sends a burst that + // gets the client throttled partway through and fails a member who was + // only trying to spend their own note. A short pause costs seconds and + // removes the failure entirely. + if n > 0 { + std::thread::sleep(std::time::Duration::from_millis(120)); + } let Some(tx) = chain.transaction(signature)? else { continue; }; From 8843aaacde2c0e85ac30d404a1f190cad5fab3bf Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 06:02:29 -0300 Subject: [PATCH 70/83] fix: three ways settlement could build a transaction the cluster refuses MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Found by settling two dozen spends against devnet, which is the first time any of them could happen. **The account-lock limit is what binds once a table is in play.** Twenty members through a lookup table weigh 332 bytes of 1232 — the packet is not remotely the constraint any more — and devnet refused them for locking 77 accounts. The SDK exports MAX_TX_ACCOUNT_LOCKS = 128, but that is the raised limit and it is not live here; the number a client must plan against is 64 until it can see otherwise, so that is the number, with a comment saying where it came from. Settlement now adds members while the batch still fits and defers the rest, so a settler never has to know the limit exists. **The crowd rule was checked against the wrong batch.** It ran before the lock limit truncated the batch, so a pool with 24 pending and a floor of 24 passed the check, sent 20, and was refused by the program with a bare 0x15. It now checks what is actually being sent, and when the floor is above what one transaction can carry it says so — that pool can only ever settle on the timeout. **A lookup table must be derived from a finalized slot.** The address is seeded with a slot the program validates against SlotHashes, which holds rooted slots only, so a confirmed slot — routinely ahead of rooted — produces an address rejected with "is not a recent slot": an error that names the cause and sounds like its opposite, since the slot was too new. And init-pool now refuses a floor higher than one settlement can carry rather than letting it be discovered later. A pool's floor is fixed at creation, so by the time anyone notices, the fix is a different pool. That mistake is how the lock limit was found. --- crates/mirror-cli/src/chain.rs | 17 +++--- crates/mirror-cli/src/client.rs | 96 +++++++++++++++++++++++++++------ crates/mirror-cli/src/lookup.rs | 27 ++++++++++ 3 files changed, 118 insertions(+), 22 deletions(-) diff --git a/crates/mirror-cli/src/chain.rs b/crates/mirror-cli/src/chain.rs index f673c62b..05a7dac9 100644 --- a/crates/mirror-cli/src/chain.rs +++ b/crates/mirror-cli/src/chain.rs @@ -143,15 +143,20 @@ impl Chain { Ok(sig) } - /// The cluster's current slot. + /// A slot recent enough to derive a lookup table's address from, and settled + /// enough that the address lookup table program will accept it. /// - /// Needed because a lookup table's address is derived from a *recent* slot, - /// and the runtime refuses one that is not. A slot read from anywhere but - /// the cluster about to receive the transaction is a guess. - pub fn slot(&self) -> Result { + /// **Finalized, deliberately.** A table's address is seeded with a slot, and + /// the program validates that slot against the `SlotHashes` sysvar — which + /// holds rooted slots only. A *confirmed* slot is routinely ahead of what is + /// rooted, so deriving from one produces a table address the program rejects + /// with `is not a recent slot`, an error that names the right cause and + /// sounds like the opposite of the problem: the slot was too new, not too + /// old. + pub fn recent_slot(&self) -> Result { let v = self.call( "getSlot", - serde_json::json!([{ "commitment": "confirmed" }]), + serde_json::json!([{ "commitment": "finalized" }]), )?; v.as_u64().ok_or_else(|| anyhow!("slot missing")) } diff --git a/crates/mirror-cli/src/client.rs b/crates/mirror-cli/src/client.rs index d41e53ec..a6054e18 100644 --- a/crates/mirror-cli/src/client.rs +++ b/crates/mirror-cli/src/client.rs @@ -88,6 +88,27 @@ pub fn init_pool( println!("nothing to do — one pool per denomination is the whole point"); return Ok(()); } + // A floor above what one settlement can carry is a floor no crowd can ever + // satisfy. Settlement locks three accounts per member plus three for the + // pool itself, so the largest batch a transaction can hold is fixed by the + // runtime and not by this program — and a pool asking for more than that can + // only ever settle through the liveness timeout, an hour at a time, which is + // the opposite of what a high floor is chosen for. + // + // Refused rather than warned about: a pool's floor is fixed at creation, so + // by the time anyone notices, the fix is a different pool. + let settleable = (lookup::MAX_ACCOUNT_LOCKS - 3) / 3; + if k_floor as usize > settleable { + return Err(anyhow!( + "a floor of {k_floor} is higher than one settlement can carry. A transaction \ + may lock {} accounts and each member costs three, so at most {settleable} \ + members settle together — a pool with this floor would never meet it by \ + crowd, and could only ever settle on the {}s timeout. Choose {settleable} \ + or fewer.", + lookup::MAX_ACCOUNT_LOCKS, + mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS, + )); + } let ix = Instruction::new_with_bytes( *program_id, &MirrorIx::InitPool { @@ -481,8 +502,54 @@ pub fn settle( return Ok(()); } - // The crowd rule, checked here so a caller learns why rather than reading a - // custom error code off a failed transaction. + // A lookup table takes the packet out of the way, and what takes over is the + // number of accounts a transaction may lock. Members are added while the + // batch still fits under it, so a settler is never handed a transaction the + // cluster will refuse — and never has to know the limit exists. + let base = vec![ + AccountMeta::new(settler.pubkey(), true), + AccountMeta::new(state.pool, false), + AccountMeta::new(state.vault, false), + ]; + let mut metas = base.clone(); + let mut taken = 0usize; + for (spend, beneficiary, relay, _) in &ready { + let mut next = metas.clone(); + next.push(AccountMeta::new(*spend, false)); + next.push(AccountMeta::new(*beneficiary, false)); + next.push(AccountMeta::new(*relay, false)); + if lookup::locks_for(&next, program_id, &settler.pubkey()) > lookup::MAX_ACCOUNT_LOCKS { + break; + } + metas = next; + taken += 1; + } + if taken == 0 { + return Err(anyhow!( + "a single spend already locks more than the {} accounts a transaction may hold", + lookup::MAX_ACCOUNT_LOCKS + )); + } + let deferred = ready.len() - taken; + if deferred > 0 { + println!(); + println!( + " {} spend(s) pending; settling {taken} of them, which is what fits under the", + ready.len() + ); + println!( + " {}-account lock limit. Run this again for the rest — the cost is a", + lookup::MAX_ACCOUNT_LOCKS + ); + println!(" second timestamp, which is a real cost to the anonymity of both halves."); + } + ready.truncate(taken); + + // The crowd rule, checked against the batch actually being sent rather than + // against everything pending. Those differ whenever the lock limit truncates + // a batch, and checking the wrong one builds a transaction the program then + // refuses with a bare error code — which is exactly what this check exists + // to spare a caller. let crowd = ready.len() as u32 >= state.k_floor; let timeout = mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS; if !crowd { @@ -491,10 +558,17 @@ pub fn settle( if waited < timeout { println!(); println!( - " {} spend(s) pending, and this pool's floor is {}.", + " this batch carries {} spend(s) and the pool's floor is {}.", ready.len(), state.k_floor ); + if deferred > 0 { + println!( + " {} more are pending but cannot join: the floor is above what one", + deferred + ); + println!(" transaction can settle, so this pool can only settle on the timeout."); + } println!( " A batch below the floor may settle once every spend in it has waited {timeout}s;" ); @@ -509,16 +583,6 @@ pub fn settle( } } - let mut metas = vec![ - AccountMeta::new(settler.pubkey(), true), - AccountMeta::new(state.pool, false), - AccountMeta::new(state.vault, false), - ]; - for (spend, beneficiary, relay, _) in &ready { - metas.push(AccountMeta::new(*spend, false)); - metas.push(AccountMeta::new(*beneficiary, false)); - metas.push(AccountMeta::new(*relay, false)); - } let ix = Instruction::new_with_bytes( *program_id, &MirrorIx::SettleEpoch { @@ -600,7 +664,7 @@ fn settle_through_lookup_table( let authority = settler.pubkey(); let addresses = lookup::addresses_for(&ix.accounts, &ix.program_id); - let recent_slot = chain.slot()?; + let recent_slot = chain.recent_slot()?; let (create_ix, table) = lookup::create(&authority, &authority, recent_slot)?; send(chain, create_ix, &[settler])?; println!(" lookup table {table}"); @@ -616,8 +680,8 @@ fn settle_through_lookup_table( // cluster to move on is not politeness, it is the difference between a // settlement that lands and one rejected for an address the table already // holds. - let extended_at = chain.slot()?; - while chain.slot()? <= extended_at { + let extended_at = chain.recent_slot()?; + while chain.recent_slot()? <= extended_at { std::thread::sleep(std::time::Duration::from_millis(400)); } diff --git a/crates/mirror-cli/src/lookup.rs b/crates/mirror-cli/src/lookup.rs index 2e5cc13d..51bea2e4 100644 --- a/crates/mirror-cli/src/lookup.rs +++ b/crates/mirror-cli/src/lookup.rs @@ -35,6 +35,19 @@ pub const PROGRAM: &str = "AddressLookupTab1e1111111111111111111111111"; /// fitting. Extending in chunks is the normal way to fill a table. pub const ADDRESSES_PER_EXTEND: usize = 20; +/// How many distinct accounts one transaction may lock. +/// +/// **Measured against devnet, not read out of the SDK.** `solana-transaction` +/// exports `MAX_TX_ACCOUNT_LOCKS = 128`, and that is the raised limit, live only +/// where the feature gate for it is active. A settlement naming 77 accounts was +/// refused by devnet with `TooManyAccountLocks`, so the number a client must +/// plan against is the older one until it can see otherwise. +/// +/// This is what actually bounds a settlement once a lookup table has taken the +/// packet out of the way. Twenty-four members through a table weigh 356 bytes of +/// 1232 — nowhere near the wire — and are refused for locking 77 accounts. +pub const MAX_ACCOUNT_LOCKS: usize = 64; + /// Slots a table must sit deactivated before it can be closed. /// /// The cooldown exists because a transaction already in flight may still resolve @@ -150,6 +163,20 @@ pub fn close(table: &Pubkey, authority: &Pubkey, recipient: &Pubkey) -> Result usize { + let mut seen: Vec = vec![*program, *payer]; + for m in metas { + if !seen.contains(&m.pubkey) { + seen.push(m.pubkey); + } + } + seen.len() +} + pub fn addresses_for(metas: &[AccountMeta], program: &Pubkey) -> Vec { let mut out: Vec = Vec::new(); for m in metas { From 21ff6cefa2c4067e36967a019742390187b6b170 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 07:04:36 -0300 Subject: [PATCH 71/83] docs: the measured settlement, and a claim the lookup table falsified MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Publishes what the devnet run took rather than what the arithmetic predicted: twenty members in one transaction, one signature, 332 bytes of 1232, 64 account locks, 35,895 CU. The correction matters more than the number. CROWD.md said that at the six-member divergent ceiling "the only two exits are closed at once" — the packet, and the compute-budget instruction that no longer fits beside it. That was true when it was written and a lookup table is a third exit, so it is false now. What replaces it says which ceiling is which: six is the *legacy* ceiling for divergent delegations, and the ceiling through a table is unmeasured for that shape. Asserting the legacy number carries over would be the same mistake in the other direction. The lookup test now asserts both constraints in one place, because "sixty members fit inside a packet" is a statement about bytes and a reader could take it for a member count — sixty members lock 183 accounts and the cluster would refuse them. Twenty is exactly 64 locks, and twenty-one is over; both are pinned. Also fixes the subcommand accounting, which had drifted twice: the count was stale after three new commands, and my first correction left init-pool out of its own list. --- README.md | 53 +++++++++++++++++++------- crates/mirror-cli/src/crowd.rs | 26 ++++++++----- crates/mirror-cli/src/lookup.rs | 28 ++++++++++++-- docs/CROWD.md | 4 +- docs/USAGE.md | 67 ++++++++++++++++++++++++++++----- 5 files changed, 140 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 5713f629..1a393a44 100644 --- a/README.md +++ b/README.md @@ -144,12 +144,16 @@ mirror spend --program $P --note m1.json \ mirror settle --program $P --denomination D # permissionless ``` -`docs/USAGE.md` is the walkthrough for these six, and every line of output in it -was produced by running the command against devnet. The other ten subcommands -are for operating a pool (`setup`, `verify-setup`, `soak`, `crowd`) and for the -measurement (`check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, -`selection`); `--help` documents each, and `docs/MEASUREMENT_LOG.md` gives the -exact invocation for every published number. +`docs/USAGE.md` is the walkthrough, and every line of output in it was produced +by running the command against devnet. It covers three more a member may want: +`init-pool`, which anyone can run, and `disclose` / `disclose-verify`, which +prove a settled action was yours to one verifier you choose. + +The other eleven subcommands operate a pool (`setup`, `verify-setup`, `soak`, +`crowd`, `close-table`) or run the measurement (`check-endpoint`, `seeds`, +`collect`, `analyze`, `compare`, `selection`); `--help` documents each, and +`docs/MEASUREMENT_LOG.md` gives the exact invocation for every published +number. **No server, no indexer, no account with anybody.** The program stores only the accumulator's frontier — enough to append a leaf, not enough to prove one is @@ -231,18 +235,39 @@ settled 10 spends in one transaction: 1228 bytes (4 to spare), 19545 CU of 20000 while the SVM settled the same batch in 24158 CU, so compute is not the constraint ``` -**With an address lookup table**, the same accounts are named by one byte each +**With an address lookup table**, the same accounts are named by one byte each, and the packet stops being the thing in the way at all. `mirror settle` publishes -a table automatically for any batch that will not fit legacy, and -`a_lookup_table_takes_the_packet_out_of_the_way` pins the arithmetic against the -executed 1228-byte figure above so the two cannot drift apart: sixty members -still fit inside a packet, six times what legacy allows and nowhere near a limit. +a table automatically for any batch that will not fit legacy. Twenty members +settled that way on devnet: + +``` +20 spends do not fit a legacy transaction: 2218 bytes, 986 over the 1232-byte packet. +Settling through a lookup table instead. + settlement is 332 bytes of 1232, one signature +``` + +[`enxa9fztmzEHM…`](https://explorer.solana.com/tx/enxa9fztmzEHMLsvhfzJwFVRsNWha7WiSEUEeNn7UPk8KAdpnWQDzEHarGL8d4ckyBvtWrEGCkuvgW7UFgFAHzp?cluster=devnet) +— twenty payouts, `numRequiredSignatures: 1`, 2 static keys and 62 resolved +through the table, 35,895 CU. Twenty recipients and twenty relays are named in +that transaction and **not one of them signed it**. Nothing in the program changes for this. `settle_epoch` requires a signature from the settler and from nobody else, and a lookup table can serve any account that is not a signer — so the ceiling was always a property of what the client chose to build, and ten is the floor rather than the maximum. +**What binds instead is the account-lock limit**, and that number was taken the +hard way: a batch of 24 was refused by devnet with `TooManyAccountLocks` at 77 +accounts. `solana-transaction` exports `MAX_TX_ACCOUNT_LOCKS = 128`, but that is +the raised limit and it is not live here, so a client must plan against 64 until +it can see otherwise. Three accounts per member plus three for the pool puts +twenty members at exactly 64 locks — the transaction above sits on the limit. + +Settlement adds members while the batch still fits and defers the rest, so a +settler is never handed a transaction the cluster will refuse, and `init-pool` +refuses a crowd floor higher than one settlement can carry rather than letting a +pool be created that can only ever settle on its timeout. + **The table is not free, and it is taken back down.** It costs four extra transactions, a slot of latency, and rent — and, more to the point, while it exists it is a public durable account listing every address the settlement is @@ -270,8 +295,10 @@ instruction budget. It is much closer for delegations. On devnet the six-member divergent batch burned 142,856 of the 200,000 CU a single instruction gets, and `CROWD.md` reports where that leaves a settler: **at that ceiling both exits are shut**. Asking for a larger compute budget costs a second instruction, measured -at 40 bytes against that very batch, and the batch has 38 to spare. Raising the -budget means dropping a member. +at 40 bytes against that very batch, and a legacy batch has 38 to spare — so in a +legacy transaction, raising the budget means dropping a member. A lookup table +lifts that too, and how far for a batch of delegations is unmeasured: `CROWD.md` +says so rather than assuming the legacy number carries over. `ten_spends_fit_in_one_settlement_and_the_packet_is_what_stops_the_eleventh` demonstrates the payment limit from both sides: it measures the eleventh batch diff --git a/crates/mirror-cli/src/crowd.rs b/crates/mirror-cli/src/crowd.rs index a6a2c5c0..e01680fd 100644 --- a/crates/mirror-cli/src/crowd.rs +++ b/crates/mirror-cli/src/crowd.rs @@ -1028,19 +1028,25 @@ fn report(outcome: &Outcome) -> String { cu as f64 * 100.0 / DEFAULT_COMPUTE_BUDGET as f64 )); md.push_str(&format!( - "The packet still binds first — {} members is where the bytes run out, and the \ - budget is not exhausted there — but the two limits are no longer independent, and \ - that is the part worth stating plainly. The usual answer to a settlement that runs \ - out of compute is to ask for more with a `SetComputeUnitLimit` instruction. \ - Measured against this very batch, that instruction costs **{} bytes**, and a full \ - settlement has {} to spare. **Raising the budget means dropping a member.**\n\n\ - So the ceiling here is not a byte count that a future compute optimisation would \ - lift. It is the point where the only two exits are closed at once, and the answer \ - to a larger crowd is more transactions — which means more timestamps, which is a \ - real cost to the anonymity and the reason this number is worth knowing.\n\n", + "The packet binds first — {} members is where the bytes run out, and the budget is \ + not exhausted there — but for a **legacy** transaction the two limits are barely \ + independent, and that is worth stating plainly. The usual answer to a settlement \ + that runs out of compute is to ask for more with a `SetComputeUnitLimit` \ + instruction. Measured against this very batch, that instruction costs **{} \ + bytes**, and a full legacy settlement has {} to spare. In a legacy transaction, \ + raising the budget means dropping a member.\n\n\ + **A lookup table lifts that, and this run did not measure how far.** Naming \ + accounts by one byte each takes the packet out of the way — `mirror settle` does \ + it automatically, and a batch of twenty plain transfers settled that way on devnet \ + at 332 bytes of 1232. What then binds a batch of *delegations* is some combination \ + of the 64-account lock limit and the compute this table already shows to be \ + expensive, and neither has been measured for this shape. The honest statement is \ + that {} is the legacy ceiling for divergent delegations and the ceiling through a \ + table is unmeasured — not that it is the same number.\n\n", ceiling.divergent, ceiling.budget_bytes, PACKET_DATA_SIZE - ceiling.divergent_bytes, + ceiling.divergent, )); md.push_str("## Every step\n\n"); diff --git a/crates/mirror-cli/src/lookup.rs b/crates/mirror-cli/src/lookup.rs index 51bea2e4..0f281444 100644 --- a/crates/mirror-cli/src/lookup.rs +++ b/crates/mirror-cli/src/lookup.rs @@ -325,15 +325,35 @@ mod tests { .len() }; - // Sixty members is six times what the packet allows without a table, and - // it is not close to the limit — which is the whole finding. Ten is the - // floor a settler gets with no setup at all, not the most the program - // can carry. + // Sixty members is six times what the packet allows without a table and + // still nowhere near it — which is the finding, and it is a statement + // about *bytes only*. Sixty members lock 183 accounts and the cluster + // would refuse them; what a settlement can actually carry is bounded by + // MAX_ACCOUNT_LOCKS, asserted just below, and a reader who took the byte + // figure for a member count would be reading the wrong constraint. let sixty = versioned_at(60); assert!( sixty < PACKET_DATA_SIZE, "sixty members through a table weigh {sixty} bytes, over the packet" ); + + // The limit that does bind, and the arithmetic settlement uses to stop + // short of it. Twenty members is exactly 64 locks: three per member plus + // the settler, the pool and the vault. + let payer = Pubkey::new_unique(); + let program = Pubkey::new_unique(); + let twenty = settle_shaped(&payer, &program, 20); + assert_eq!( + locks_for(&twenty.accounts, &program, &payer), + MAX_ACCOUNT_LOCKS, + "twenty members is supposed to sit exactly on the lock limit" + ); + let twenty_one = settle_shaped(&payer, &program, 21); + assert!( + locks_for(&twenty_one.accounts, &program, &payer) > MAX_ACCOUNT_LOCKS, + "twenty-one members must be over the lock limit, or settlement stops \ + one short of what it could carry" + ); assert!( versioned_at(10) < legacy_at(10) / 2, "a table is supposed to cost far less per member than naming keys in full" diff --git a/docs/CROWD.md b/docs/CROWD.md index d85e7f26..b1c4a196 100644 --- a/docs/CROWD.md +++ b/docs/CROWD.md @@ -49,9 +49,9 @@ This settlement burned **142856 CU of the 200000** a single instruction gets by Each member's Groth16 proof was verified earlier, in their own `submit_spend`, which costs about 101,000 CU. That is why the two phases exist: verifying 6 proofs here would cost over 600,000 CU — comfortably past the 200,000 a single instruction gets by default, and a large fraction of the 1.4M a whole transaction may ever request. -The packet still binds first — 6 members is where the bytes run out, and the budget is not exhausted there — but the two limits are no longer independent, and that is the part worth stating plainly. The usual answer to a settlement that runs out of compute is to ask for more with a `SetComputeUnitLimit` instruction. Measured against this very batch, that instruction costs **40 bytes**, and a full settlement has 38 to spare. **Raising the budget means dropping a member.** +The packet binds first — 6 members is where the bytes run out, and the budget is not exhausted there — but for a **legacy** transaction the two limits are barely independent, and that is worth stating plainly. The usual answer to a settlement that runs out of compute is to ask for more with a `SetComputeUnitLimit` instruction. Measured against this very batch, that instruction costs **40 bytes**, and a full legacy settlement has 38 to spare. In a legacy transaction, raising the budget means dropping a member. -So the ceiling here is not a byte count that a future compute optimisation would lift. It is the point where the only two exits are closed at once, and the answer to a larger crowd is more transactions — which means more timestamps, which is a real cost to the anonymity and the reason this number is worth knowing. +**A lookup table lifts that, and this run did not measure how far.** Naming accounts by one byte each takes the packet out of the way — `mirror settle` does it automatically, and a batch of twenty plain transfers settled that way on devnet at 332 bytes of 1232. What then binds a batch of *delegations* is some combination of the 64-account lock limit and the compute this table already shows to be expensive, and neither has been measured for this shape. The honest statement is that 6 is the legacy ceiling for divergent delegations and the ceiling through a table is unmeasured — not that it is the same number. ## Every step diff --git a/docs/USAGE.md b/docs/USAGE.md index a2c2e080..138a6dc6 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -286,10 +286,57 @@ It depends on what the members are doing: | stake delegations, everyone to the same validator | 7 | | stake delegations, a different validator each | 6 | -The limit is the 1232-byte packet in every row. Each spend brings accounts -nobody else shares — its record, its beneficiary, its relay — so a payment costs -about 99 bytes per member, and a call costs more because it also names its -callee and the callee's accounts. +Those are the numbers for a **legacy** transaction, which names every account by +its full 32 bytes. That is what you get with no setup at all, and the 1232-byte +packet is what stops it. + +**Bigger batches settle through a lookup table, and `settle` does it for you.** +When a batch will not fit legacy, the command publishes a table naming the +accounts, settles a v0 transaction that refers to them by one byte each, and +takes the table back down: + +``` +$ mirror settle --program 8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa --denomination 5000003 +looking for spends waiting to settle: + 49 transactions touched this pool + + 24 spend(s) pending; settling 20 of them, which is what fits under the + 64-account lock limit. Run this again for the rest — the cost is a + second timestamp, which is a real cost to the anonymity of both halves. + + 20 spends do not fit a legacy transaction: 2218 bytes, 986 over the 1232-byte packet. + Settling through a lookup table instead. + lookup table ia9oUXMgArZhGPWyETroygf6bvULHQWBodgpwB43gQ8 + 62 addresses published + settlement is 332 bytes of 1232, one signature + table deactivated. Close it after ~513 slots to reclaim the rent and remove + the published address list: + mirror close-table --table ia9oUXMgArZhGPWyETroygf6bvULHQWBodgpwB43gQ8 + +settled 20 spends in one transaction + signature enxa9fztmzEHMLsvhfzJwFVRsNWha7WiSEUEeNn7UPk8KAdpnWQDzEHarGL8d4ckyBvtWrEGCkuvgW7UFgFAHzp +``` + +**The packet stops mattering entirely** — twenty members weigh 332 of 1232 +bytes. What binds instead is the number of accounts one transaction may lock: +three per member plus three for the pool, so twenty members sit at exactly 64. +`settle` counts them and defers the rest rather than building a transaction the +cluster refuses. + +**Close the table afterwards.** It costs rent, but the reason to close it is what +it is while it exists: a public, durable account listing every address the +settlement touched, published before the settlement landed. Leaving one behind +per batch builds a permanent on-chain index of who settled together. + +**Do not set a crowd floor above what one settlement can carry.** `init-pool` +refuses it now, because a pool whose floor exceeds the per-transaction ceiling +can never meet it by crowd and can only settle through the hour-long timeout — +and the floor is fixed at creation, so the fix is a different pool. + +For a legacy batch the limit is the packet in every row. Each spend brings +accounts nobody else shares — its record, its beneficiary, its relay — so a +payment costs about 99 bytes per member, and a call costs more because it also +names its callee and the callee's accounts. **What you ask for changes how many people you can hide among.** A member who delegates to their own choice of validator adds a vote account nobody else in @@ -298,11 +345,13 @@ choose: a crowd that converges on one validator is both larger and less distinguishable than a crowd that does not. For payments compute is nowhere near binding — ten settle in 19,545 of the -200,000 compute units a single instruction gets. For delegations it is much -closer: on devnet the six-member divergent batch used 142,856. And at that -ceiling there is no way out, because the two limits shut together — asking for a -larger compute budget costs a second instruction worth 40 bytes, and the batch -has 38 to spare. +200,000 compute units a single instruction gets, and twenty through a lookup +table in 35,895. For delegations it is much closer: on devnet the six-member +divergent batch used 142,856. In a *legacy* transaction there is no way out of +that, because the two limits shut together — asking for a larger compute budget +costs a second instruction worth 40 bytes and the batch has 38 to spare. Through +a table the bytes are there; how many delegations then fit is unmeasured, and +`CROWD.md` says so rather than assuming. These are ceilings per *transaction*, not per epoch. Settlement is permissionless, so a pool with thirty pending spends settles in three batches; From 57715e2ffca70d085d9e6d5dc35c6629b6dde121 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 07:05:20 -0300 Subject: [PATCH 72/83] docs: the lookup table is a disclosure, and the threat model now says so --- docs/THREAT_MODEL.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 500ec73c..2a43bde4 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -212,6 +212,33 @@ clause is a discipline the member keeps, not a property the program enforces, an a threat model that omits it is describing a smaller adversary than the one that exists. +### A large settlement publishes its participant list early + +A batch that outgrows the 1232-byte packet settles through an address lookup +table, and that table is an account: created by the settler, holding every +address the settlement is about to touch, and **on chain before the settlement +lands**. It discloses nothing the settlement does not disclose a slot later — the +same records, beneficiaries and relays, in the same order — but it discloses it +*earlier*, and while it exists it is a durable, queryable index of who settled +together, tied to the key that created it. + +Two consequences worth stating rather than discovering: + +- **Timing.** An observer watching the lookup table program sees the batch + assembling before it executes. That is a warning, not a linkage: the addresses + in the table are the same public addresses the settlement names. +- **Persistence.** A table left behind outlives the transaction that needed it, + and one per batch accumulates into a permanent record of every cohort a pool + ever settled. + +So settlement deactivates the table immediately and `mirror close-table` removes +it once the runtime's cooldown has passed, which returns the rent and — the +reason that matters here — deletes the list. The cooldown means there is a window +in which the table exists and cannot yet be closed; nothing shortens it. + +Legacy settlement publishes no table at all, which is why it stays the default +for any batch that fits without one. + ### Amounts are public Fixed denominations mean the amount is a pool constant rather than a secret. A From aa14e7f2ed1fd05c5517fe7bc78408517404b548 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 07:08:14 -0300 Subject: [PATCH 73/83] docs: the disclosure and the table close, as they actually ran MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the constructed examples with devnet output. The disclosure is a real one: a member proving after the fact that the pool-signed stake delegation in CROWD.md was theirs, ten checks, every value recomputed from the secrets or read off the cluster. The table close is real too — 15,084,280 lamports back and the published address list gone, on the table the twenty-member settlement used. That is the half of the lookup-table story that is easy to skip, and skipping it leaves a permanent on-chain index of every cohort a pool ever settled. --- README.md | 7 +++++-- docs/USAGE.md | 47 +++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 1a393a44..1bd4c26c 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,9 @@ produce one. Two things guard the member instead: paying it. Thirty-one tests cover it, one per tamper case, and each asserts *which* check -failed rather than merely that verification did. +failed rather than merely that verification did. `docs/USAGE.md` shows the real +thing: a member proving, after the fact, that the **stake delegation** in +`CROWD.md` was theirs — ten checks, all recomputed, against devnet. ### How large a crowd fits in one settlement @@ -275,7 +277,8 @@ about to touch, published *before* the settlement lands. Leaving one behind per batch would turn a one-transaction event into a permanent on-chain index of the batch, which is a strange thing for a privacy pool to accumulate. So settlement deactivates it immediately and `mirror close-table` reclaims the rent and removes -the list once the runtime's cooldown has passed. +the list once the runtime's cooldown has passed — 15,084,280 lamports back, and +the address list gone, on the table the settlement above used. Under legacy, the **packet size** binds in all three rows. Each spend brings accounts nobody else shares — its record, its beneficiary, its relay — so a payment costs about 99 diff --git a/docs/USAGE.md b/docs/USAGE.md index 138a6dc6..54480bd0 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -234,19 +234,47 @@ mirror disclose --program --note m1.json --out disclosure.json ``` Give them the file. They check it against the chain, and nothing in it is taken -on trust — the commitment and the nullifier are recomputed from your secrets, the -record's address is derived from that recomputed nullifier, the accumulator is -rebuilt from history and checked against the pool's own root, and the action is -read out of the record: +on trust: ``` -mirror disclose-verify --file disclosure.json +$ mirror disclose-verify --file disclosure.json +rebuilding the pool from chain history: + 14 transactions touched this pool + +disclosure for leaf 0 of Cqg4gj4zwHZGfp1P2v6j6pB4dLbWgsL1vJB7YkjsFWAK + nullifier 25609bc4fe513e50fcc8874e1dbe8d7a556ff8b95724c606baa429aa6fdcaee2 + record 4beEKJNuU4pHM3rUqLAm8ocVkf7kzRdLP2XMECCMwy77 + + the secrets recompute to the commitment PASS + H3(k, r, denom_tag) = 13833afd2062da2c5a0217dd53b14c8118a10ded98d84f8af71853383509f2fd + the pool matches the denomination PASS + Cqg4gj4zwHZGfp1P2v6j6pB4dLbWgsL1vJB7YkjsFWAK is the pool for 43000007 lamports + the commitment is the stated leaf PASS + leaf 0 of 6 is 13833afd2062da2c5a0217dd53b14c8118a10ded98d84f8af71853383509f2fd + the rebuilt root matches the chain PASS + 6 leaves rebuild to 039bbb3012b6ff6554141437df6da9c12db6a5333c3c38e00146c85df87bfe08 + the secrets recompute to the nullifier PASS + H1(k) = 25609bc4fe513e50fcc8874e1dbe8d7a556ff8b95724c606baa429aa6fdcaee2 + the record address is the nullifier's PDA PASS + 4beEKJNuU4pHM3rUqLAm8ocVkf7kzRdLP2XMECCMwy77 is the record for H1(k) + the spend record exists on chain PASS + the spend record is settled PASS + settled + the spend record belongs to this pool PASS + the record matches the claimed action PASS + selector 2 paid 5P9AHY2tGoQ9xedLeyvC5WsU4FXxxH8LgtC2kzRUMZtT, fee 200000, 4 payload byte(s) + +VERIFIED. Every value in this file was recomputed from the secrets or read off the +cluster. The holder of this note is whoever asked the pool for the action above. ``` +That one is a real disclosure of a **stake delegation** — selector 2, the +pool-signed action from `CROWD.md` — proved by its member after the fact. + Every check is reported separately and a check that cannot be completed is a failure, never a silence. A file whose stated nullifier disagrees with what your secrets produce fails on that check while the rest still pass, which tells the -verifier exactly what was tampered with. +verifier exactly what was tampered with rather than merely that something was. **It only works after settlement, and the tool refuses before it.** The disclosure carries the note's secrets, and before the note is spent those secrets *are* the @@ -328,6 +356,13 @@ it is while it exists: a public, durable account listing every address the settlement touched, published before the settlement landed. Leaving one behind per batch builds a permanent on-chain index of who settled together. +``` +$ mirror close-table --table ia9oUXMgArZhGPWyETroygf6bvULHQWBodgpwB43gQ8 +closed ia9oUXMgArZhGPWyETroygf6bvULHQWBodgpwB43gQ8 + signature 1x3Uv5k8j8MnfWsD6rTksUkV9Mcgwvg6PjfTDJXFHgQ9J6CQ7wih51ANeBtaNXCDktstjBvupR2b5aHZPnoYuFp + reclaimed 15084280 lamports, and the published address list is gone +``` + **Do not set a crowd floor above what one settlement can carry.** `init-pool` refuses it now, because a pool whose floor exceeds the per-transaction ceiling can never meet it by crowd and can only settle through the hour-long timeout — From 3c99e411753a63276941ab734066a46efbdcf04b Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 07:25:42 -0300 Subject: [PATCH 74/83] fix: a mixed-fee batch was an anonymity set an observer could partition MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A member is paid denomination - relay_fee, and that figure lands in a public account balance. The program bounded the fee only by relay_fee < denomination, so two members settled together could receive visibly different amounts — and an observer partitions the batch by value without breaking a proof or learning a secret. The crowd rule, the shared timestamp and the single settling signature all exist to prevent exactly that partition, and a fee that varies handed it back. The check goes in settlement rather than submission because it is a property of the batch and not of any record: a member may agree any fee with their relay, and settles with the members who agreed the same one. FeeNotUniform, code 25, refused whole — neither member is paid and neither record is consumed, so both can still settle with somebody who paid what they did. mirror settle now groups pending spends by fee and takes the largest group, rather than assembling a transaction the program will reject. What this does not fix, and the threat model says so: a fee nobody else pays is still a crowd of one. The rule turns a silent partition into a visible one; choosing a common fee stays the member's job. Both tests assert the property rather than the error code alone — the refusal leaves both records pending and both beneficiaries unfunded, and the uniform batch pays every member the same number. --- README.md | 4 +- crates/mirror-cli/src/client.rs | 36 ++++- docs/THREAT_MODEL.md | 25 ++++ docs/USAGE.md | 7 + programs/mirror-pool/src/error.rs | 8 ++ programs/mirror-pool/src/processor.rs | 25 ++++ programs/mirror-pool/tests/end_to_end.rs | 168 +++++++++++++++++++++++ 7 files changed, 268 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1bd4c26c..850a24a1 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Then: ``` git clone https://github.com/solanabr/mirror-pool && cd mirror-pool -make verify # fmt, clippy -D warnings, build-sbf, 256 tests +make verify # fmt, clippy -D warnings, build-sbf, 258 tests ``` Nothing in that command needs a network, an API key or an account with anybody, @@ -126,7 +126,7 @@ not say. There is a section below of things we deliberately do not claim. | `crates/mirror-provenance` | The funding-provenance measurement. | | `crates/mirror-cli` | The tool. `init-pool`, `note-new`, `deposit`, `tree`, `spend`, `settle`, `disclose`, `disclose-verify` for members; `setup`, `verify-setup`, `soak`, `crowd`, `close-table` for operators; `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection` for the measurement. | -**256 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**258 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. diff --git a/crates/mirror-cli/src/client.rs b/crates/mirror-cli/src/client.rs index a6054e18..06b9d549 100644 --- a/crates/mirror-cli/src/client.rs +++ b/crates/mirror-cli/src/client.rs @@ -453,7 +453,7 @@ pub fn settle( println!("looking for spends waiting to settle:"); let history = history::scan(chain, program_id, &state.pool, true)?; - let mut ready: Vec<(Pubkey, Pubkey, Pubkey, i64)> = Vec::new(); + let mut ready: Vec<(Pubkey, Pubkey, Pubkey, i64, u64)> = Vec::new(); let mut skipped_actions: Vec<([u8; 32], u64, u8)> = Vec::new(); for submitted in &history.spends { let Some(mut data) = chain.account_data(&submitted.spend)? else { @@ -481,6 +481,7 @@ pub fn settle( Pubkey::new_from_array(record.beneficiary()), Pubkey::new_from_array(record.relay()), record.submitted_at(), + record.relay_fee(), )); } @@ -502,6 +503,35 @@ pub fn settle( return Ok(()); } + // Members are paid `denomination - relay_fee`, and that payout is public — so + // a batch mixing fees settles into visibly different amounts and an observer + // partitions it by value. The program refuses such a batch; this picks one + // fee and settles the members who paid it, rather than assembling a + // transaction that will be rejected. + // + // The largest group goes first, because the batch that hides its members + // best is the biggest one available. + let mut by_fee: std::collections::BTreeMap> = std::collections::BTreeMap::new(); + for entry in ready { + by_fee.entry(entry.4).or_default().push(entry); + } + let fee_groups = by_fee.len(); + let (chosen_fee, mut ready) = by_fee + .into_iter() + .max_by_key(|(_, group)| group.len()) + .expect("there is at least one pending spend"); + if fee_groups > 1 { + println!(); + println!( + " the pending spends carry {fee_groups} different relay fees. Settling the {} that", + ready.len() + ); + println!( + " paid {chosen_fee}: a batch of mixed fees pays visibly different amounts, which" + ); + println!(" lets an observer partition it by value. Run again for the others."); + } + // A lookup table takes the packet out of the way, and what takes over is the // number of accounts a transaction may lock. Members are added while the // batch still fits under it, so a settler is never handed a transaction the @@ -513,7 +543,7 @@ pub fn settle( ]; let mut metas = base.clone(); let mut taken = 0usize; - for (spend, beneficiary, relay, _) in &ready { + for (spend, beneficiary, relay, _, _) in &ready { let mut next = metas.clone(); next.push(AccountMeta::new(*spend, false)); next.push(AccountMeta::new(*beneficiary, false)); @@ -553,7 +583,7 @@ pub fn settle( let crowd = ready.len() as u32 >= state.k_floor; let timeout = mirror_pool_program::processor::SETTLE_TIMEOUT_SECONDS; if !crowd { - let youngest = ready.iter().map(|(_, _, _, at)| *at).max().unwrap_or(0); + let youngest = ready.iter().map(|(_, _, _, at, _)| *at).max().unwrap_or(0); let waited = now.saturating_sub(youngest); if waited < timeout { println!(); diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 2a43bde4..cb43e78d 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -239,6 +239,31 @@ in which the table exists and cannot yet be closed; nothing shortens it. Legacy settlement publishes no table at all, which is why it stays the default for any batch that fits without one. +### The relay fee is a payout amount, and a mixed batch is a partitioned one + +The denomination is uniform by construction, but a member receives +`denomination - relay_fee`, and that figure lands in a public account balance. So +the fee is not a private arrangement between a member and their relay: it is a +number an observer reads off the settlement. + +A batch whose members paid different fees settles into visibly different payouts, +and an observer partitions it by value. No proof is broken and no secret is +learned — the balances are simply different, and the crowd rule, the shared +timestamp and the single settling signature are all defeated by arithmetic. + +**The program refuses such a batch** (`FeeNotUniform`, code 25). The check is in +settlement rather than submission because it is a property of the batch and not +of any record: a member may agree any fee with their relay, and settles with the +members who agreed the same one. `mirror settle` groups pending spends by fee and +settles the largest group. + +What this does *not* fix: a fee that is unusual is still a small crowd. A member +who negotiates a fee nobody else pays settles alone, or with the few who match — +and a batch of one is not an anonymity set whatever the program allows. The +uniform-fee rule turns a silent partition into a visible one; choosing a common +fee is still the member's job, and a pool whose members all pay the tool's +default is better off than one where they do not. + ### Amounts are public Fixed denominations mean the amount is a pool constant rather than a secret. A diff --git a/docs/USAGE.md b/docs/USAGE.md index 54480bd0..ee58ac3e 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -151,6 +151,13 @@ wait for anyone else to. The relay fee comes *out of* the denomination, never in addition to it, and a fee at or above the denomination is refused on-chain. +**Pay what everyone else pays.** You receive `denomination − relay_fee`, and that +number is public. A batch whose members paid different fees settles into visibly +different amounts, so an observer partitions it by value without breaking +anything — which is why the program refuses a mixed batch outright and `settle` +groups pending spends by fee. A fee nobody else pays is a crowd of one, and no +rule in the program can fix that for you. + ### Doing something other than paying The interesting case is not moving lamports. `--invoke` calls any program with diff --git a/programs/mirror-pool/src/error.rs b/programs/mirror-pool/src/error.rs index af77127f..dc90ce12 100644 --- a/programs/mirror-pool/src/error.rs +++ b/programs/mirror-pool/src/error.rs @@ -71,6 +71,14 @@ pub enum MirrorProgramError { SelfInvocationRefused = 23, /// The action payload exceeds the maximum a spend record can carry. PayloadTooLarge = 24, + /// Two records in one settlement carried different relay fees. + /// + /// A member is paid `denomination - relay_fee`, and that payout is public. + /// A batch whose members paid different fees settles into visibly different + /// amounts, so an observer partitions it by value without breaking anything + /// — which costs the members the exact property the batch exists to give + /// them. Refused rather than documented. + FeeNotUniform = 25, } impl From for ProgramError { diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 4da93857..48393871 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -545,6 +545,23 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro let now = solana_program::clock::Clock::get()?.unix_timestamp; let crowd_satisfied = count as u32 >= k_floor; + // Every member in a batch must be paid the same amount, and the fee is the + // only thing that can make them differ. + // + // A member receives `denomination - relay_fee`, and that lamport figure is + // public the moment settlement lands. A batch whose members paid different + // fees therefore settles into visibly different payouts, and an observer + // partitions it by value — without breaking a proof, without knowing a + // secret, by reading the balances. The crowd rule, the shared timestamp and + // the single settling signature all exist to stop exactly that partition, + // and a fee that varies hands it back. + // + // Enforced here rather than at submission because it is a property of the + // *batch*, not of any one record: a member is free to pay whatever fee they + // agreed with their relay, and they settle with the members who agreed the + // same. Nothing stops a settler grouping by fee; this stops them mixing. + let mut batch_fee: Option = None; + // Each spend brings its record, its beneficiary and its relay. let mut pending: Vec = Vec::with_capacity(count as usize); for _ in 0..count { @@ -593,6 +610,14 @@ fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> Pro ) }; + match batch_fee { + None => batch_fee = Some(relay_fee), + Some(first) if first != relay_fee => { + return Err(MirrorProgramError::FeeNotUniform.into()) + } + Some(_) => {} + } + let (target, action_infos) = match selector { SELECTOR_TRANSFER => { if action_accounts != 0 { diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index b36ae41c..cf794a48 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -232,6 +232,68 @@ fn spend_ix( } /// Produces a proof for note `index`, bound to `beneficiary`. +/// A proof and a `submit_spend` for note `index` at an arbitrary relay fee. +/// +/// The fee is bound into the proof, so a differing fee is a differing statement +/// rather than a field a test can edit afterwards — which is why this exists +/// separately from the fixed-fee helpers. +#[allow(clippy::too_many_arguments)] +fn spend_ix_at_fee( + env: &Env, + keys: &Keys, + tree: &MerkleTree, + notes: &[Note], + index: usize, + beneficiary: &Pubkey, + relay: &Pubkey, + relay_fee: u64, +) -> ([u8; 32], Instruction) { + use ark_std::rand::SeedableRng; + let merkle_proof = tree.proof(index as u64).unwrap(); + let binding = mirror_core::action_binding( + SELECTOR, + &[0u8; 32], + &beneficiary.to_bytes(), + relay_fee, + 0, + &[], + ); + let witness = Witness { + note: notes[index], + merkle_proof: &merkle_proof, + root: tree.root().unwrap(), + action_binding: binding, + }; + let mut rng = ark_std::rand::rngs::StdRng::from_seed([index as u8; 32]); + let proof = prove(keys, &witness, &mut rng).expect("proving"); + let nullifier = proof.public_inputs[1]; + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + let ix = Instruction::new_with_bytes( + env.program_id, + &MirrorIx::SubmitSpend { + proof_a: proof.proof_a, + proof_b: proof.proof_b, + proof_c: proof.proof_c, + root: proof.public_inputs[0], + nullifier, + selector: SELECTOR, + target_program: [0u8; 32], + beneficiary: beneficiary.to_bytes(), + relay_fee, + action_accounts: 0, + payload: Vec::new(), + } + .pack(), + vec![ + AccountMeta::new(*relay, true), + AccountMeta::new(env.pool, false), + AccountMeta::new(spend_pda, false), + AccountMeta::new_readonly(solana_system_interface::program::ID, false), + ], + ); + (nullifier, ix) +} + fn proof_for( keys: &Keys, tree: &MerkleTree, @@ -1881,3 +1943,109 @@ fn squatting_a_pool_pda_does_not_prevent_the_pool() { assert_eq!(pool.denomination(), DENOMINATION); assert_eq!(pool.k_floor(), K_FLOOR); } + +/// A batch whose members paid different relay fees settles into visibly +/// different payouts, and that is an anonymity set an observer can partition by +/// reading balances — no proof broken, no secret learned. +/// +/// The crowd rule, the single settling signature and the shared timestamp all +/// exist to prevent exactly that partition, so a mixed-fee batch is refused +/// rather than documented. It is a property of the batch and not of any record, +/// which is why the check lives in settlement: a member may agree any fee with +/// their relay, and settles with the members who agreed the same one. +#[test] +fn a_batch_whose_members_paid_different_fees_is_refused() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + + // Four members — the pool's floor — identical in every respect except what + // the last one's relay charged. + let fees = [RELAY_FEE, RELAY_FEE, RELAY_FEE, RELAY_FEE + 1]; + let mut batch = Vec::new(); + for (i, fee) in fees.iter().enumerate() { + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let (nullifier, ix) = spend_ix_at_fee( + &env, + &keys, + &tree, + ¬es, + i, + &beneficiary, + &relay.pubkey(), + *fee, + ); + env.send(ix, &relay).expect("submitting at an agreed fee"); + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + batch.push((spend_pda, beneficiary, relay)); + } + + let err = env + .send(settle_ix(&env, &batch, &settler.pubkey()), &settler) + .expect_err("a mixed-fee batch must not settle"); + assert!( + err.contains("Custom(25)"), + "expected FeeNotUniform, got {err}" + ); + + // Refused, not partially applied: neither member was paid and neither record + // was consumed, so both can still settle with somebody who paid what they did. + for (spend, beneficiary, _) in &batch { + let mut data = env.svm.get_account(spend).unwrap().data; + assert_eq!(Spend::load(&mut data).unwrap().status(), STATUS_PENDING); + // The beneficiary was never funded, so the account does not exist at + // all — which is the strongest form of "nothing was paid". + assert_eq!( + env.svm + .get_account(beneficiary) + .map(|a| a.lamports) + .unwrap_or(0), + 0 + ); + } +} + +/// The same two members, once their fees agree, settle normally — so the check +/// above rejects the mixture and not the members. +#[test] +fn the_same_members_settle_once_their_fees_agree() { + let (mut env, tree, notes, keys) = seeded_pool(5); + let settler = Keypair::new(); + env.svm.airdrop(&settler.pubkey(), 10_000_000_000).unwrap(); + + let odd_fee = RELAY_FEE + 1; + let mut batch = Vec::new(); + for i in 0..4 { + let beneficiary = Pubkey::new_unique(); + let relay = Keypair::new(); + env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); + let (nullifier, ix) = spend_ix_at_fee( + &env, + &keys, + &tree, + ¬es, + i, + &beneficiary, + &relay.pubkey(), + odd_fee, + ); + env.send(ix, &relay).expect("submitting"); + let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); + batch.push((spend_pda, beneficiary, relay)); + } + + env.send(settle_ix(&env, &batch, &settler.pubkey()), &settler) + .expect("a uniform batch settles whatever the fee is"); + + // Equal payouts is the property, and it is the thing to assert. + let paid: Vec = batch + .iter() + .map(|(_, b, _)| env.svm.get_account(b).unwrap().lamports) + .collect(); + assert!( + paid.iter().all(|p| *p == DENOMINATION - odd_fee), + "members were paid different amounts: {paid:?}" + ); +} From 15c56de52dd90cdf878ad5ca65c5614cf696081f Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Mon, 27 Jul 2026 11:20:55 -0300 Subject: [PATCH 75/83] docs: the batching trade named, and the metric turned on ourselves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **The crowd rule is threshold-or-timeout and the timeout side has no floor.** A batch of one settles and executes, an hour after submission, and settlement is permissionless so an adversary may be the settler and compose that batch. k_floor bounds a batch that settles by crowd and bounds nothing about one that settles by clock. The threat model now argues that rather than leaving it to be found: why the trade is still right — the alternative freezes a quiet pool's escrow with no authority able to release it — what a member can do instead, which is traffic and not the program, and what would actually fix it, which is refunding rather than executing a batch that never reached the floor. That is a different protocol and it is named because it is the honest answer. `mirror settle` now warns when it is about to publish an under-floor batch, and says plainly that a batch of one gives an anonymity set of one. **And CROWD.md runs our own metric against our own crowd.** It returns ρ = 1.0000, the best value the metric can produce, and the section exists to say why that is worthless: every note in that pool was funded by one wallet, so the partition has a single class and the class is us. The metric correctly reports no loss through the provenance channel and cannot report that the channel which failed was a different one. That is the tautology PROVENANCE_METHOD §9.0 warns about, met head-on. A measurement apparatus that only ever points outward is one nobody has tested, and the published headline is only worth anything because it points at a pool this project neither controls nor funded. --- README.md | 7 ++++ crates/mirror-cli/src/client.rs | 23 ++++++++++++++ crates/mirror-cli/src/crowd.rs | 42 ++++++++++++++++++++++++ docs/CROWD.md | 16 ++++++++++ docs/THREAT_MODEL.md | 46 +++++++++++++++++++++++++++ programs/mirror-pool/src/processor.rs | 9 ++++++ 6 files changed, 143 insertions(+) diff --git a/README.md b/README.md index 850a24a1..34975114 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,13 @@ So this project claims exactly two things: itself refused to publish a headline from and the one whose pre-registered prediction turned out wrong. + **And the metric is turned on this project too.** `docs/CROWD.md` runs it + against our own devnet crowd, where it returns ρ = 1.0000 — the best value it + can produce — and then says why that number is worthless: every note in that + pool was funded by one wallet, so the partition has one class and the class is + us. A measurement that only ever points outward is a measurement nobody has + tested. + Anything we cannot support with a measurement whose method is published, we do not say. There is a section below of things we deliberately do not claim. diff --git a/crates/mirror-cli/src/client.rs b/crates/mirror-cli/src/client.rs index 06b9d549..7148a085 100644 --- a/crates/mirror-cli/src/client.rs +++ b/crates/mirror-cli/src/client.rs @@ -611,6 +611,29 @@ pub fn settle( println!(" hold a member's funds waiting for a crowd that never arrives."); return Ok(()); } + + // Past the timeout the program will accept this batch whatever its size, + // and a settler about to publish an under-floor batch should be told what + // it buys the members in it — which, at one member, is nothing. The + // timeout is a solvency guarantee and it is not an anonymity one. + println!(); + println!( + " WARNING: this batch carries {} spend(s), below the pool's floor of {}.", + ready.len(), + state.k_floor + ); + println!(" It settles because every spend in it has waited out the {timeout}s timeout,"); + println!(" which is a solvency guarantee and not an anonymity one. The members in"); + if ready.len() == 1 { + println!(" this batch get an anonymity set of exactly one: the action will be"); + println!(" attributable to whoever submitted it. Wait for company if you can."); + } else { + println!( + " this batch get an anonymity set of {}, not {}.", + ready.len(), + state.k_floor + ); + } } let ix = Instruction::new_with_bytes( diff --git a/crates/mirror-cli/src/crowd.rs b/crates/mirror-cli/src/crowd.rs index e01680fd..e003af3d 100644 --- a/crates/mirror-cli/src/crowd.rs +++ b/crates/mirror-cli/src/crowd.rs @@ -1072,6 +1072,48 @@ fn report(outcome: &Outcome) -> String { cluster rather than against this file.\n\n" )); + md.push_str("## What this run's own anonymity was, by our own metric\n\n"); + md.push_str( + "It would be easy to publish this section's numbers and let a reader take them \ + for a privacy result. They are not one, and the honest way to show that is to \ + run the measurement this repository is built around against *this run* rather \ + than only against somebody else's pool.\n\n", + ); + // Every note here was deposited by one wallet, so the funding-provenance + // partition has exactly one class. Computed by the same code that produces + // the published headline rather than asserted, because the point of the + // section is that the number comes out *good* and means nothing. + let own = mirror_provenance::Anonymity::from_class_sizes(&[members as u64]); + if let Some(a) = own { + md.push_str(&format!( + "| quantity | this run |\n|---|---|\n\ + | nominal k | {} |\n\ + | provenance classes | {} |\n\ + | ρ, the loss factor | {:.4} |\n\ + | effective k (Shannon) | {:.2} |\n\ + | effective k (min-entropy) | {:.2} |\n\n", + a.nominal_k, a.classes, a.loss_factor, a.eff_k_shannon, a.eff_k_min_entropy + )); + md.push_str(&format!( + "**ρ = {:.4} is the best value the metric can return, and it is meaningless \ + here.** Every note in this pool was deposited by the same wallet, so the \ + partition has one class holding all {} members; an adversary who learns a \ + member's funding class learns nothing, and the metric correctly reports no \ + loss *through that channel*. What it cannot report is that the single class \ + is the operator, who funded every deposit and every relay and therefore knows \ + which member is which. Against that adversary the anonymity set is **one**, \ + and no funding-provenance number will ever say so, because provenance is not \ + the channel that failed.\n\n\ + This is the shape of the tautology `PROVENANCE_METHOD.md` §9.0 warns about, \ + met head-on: a metric applied to a population constructed by the person \ + reading it returns whatever that construction implies. The published headline \ + in `README.md` avoids it by pointing at a pool this project does not control \ + and did not fund — which is the only reason that number means anything and \ + this one does not.\n\n", + a.loss_factor, a.nominal_k + )); + } + md.push_str("## Scope\n\n"); md.push_str( "Devnet, and one operator. This is a functional and quantitative result, not an \ diff --git a/docs/CROWD.md b/docs/CROWD.md index b1c4a196..366a0b5c 100644 --- a/docs/CROWD.md +++ b/docs/CROWD.md @@ -84,6 +84,22 @@ Every value above was written by the run that produced it, and the run recorded Every address and signature here is on devnet and can be checked against the cluster rather than against this file. +## What this run's own anonymity was, by our own metric + +It would be easy to publish this section's numbers and let a reader take them for a privacy result. They are not one, and the honest way to show that is to run the measurement this repository is built around against *this run* rather than only against somebody else's pool. + +| quantity | this run | +|---|---| +| nominal k | 6 | +| provenance classes | 1 | +| ρ, the loss factor | 1.0000 | +| effective k (Shannon) | 6.00 | +| effective k (min-entropy) | 6.00 | + +**ρ = 1.0000 is the best value the metric can return, and it is meaningless here.** Every note in this pool was deposited by the same wallet, so the partition has one class holding all 6 members; an adversary who learns a member's funding class learns nothing, and the metric correctly reports no loss *through that channel*. What it cannot report is that the single class is the operator, who funded every deposit and every relay and therefore knows which member is which. Against that adversary the anonymity set is **one**, and no funding-provenance number will ever say so, because provenance is not the channel that failed. + +This is the shape of the tautology `PROVENANCE_METHOD.md` §9.0 warns about, met head-on: a metric applied to a population constructed by the person reading it returns whatever that construction implies. The published headline in `README.md` avoids it by pointing at a pool this project does not control and did not fund — which is the only reason that number means anything and this one does not. + ## Scope Devnet, and one operator. This is a functional and quantitative result, not an anonymity claim about a live crowd: the relays here were funded from the same wallet that made the deposits, which is exactly the linkage `USAGE.md` tells a real member to avoid. What the run establishes is that a batch of *divergent* actions settles as one, that every member reached the validator they chose, and what such a batch costs in packet space. diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index cb43e78d..2757f602 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -270,6 +270,52 @@ Fixed denominations mean the amount is a pool constant rather than a secret. A member who needs an unusual amount is identifiable by the pool they chose. There is no confidential-value layer here. +### The crowd rule is threshold-or-timeout, and the timeout side has no floor + +A batch settles if it carries `k_floor` spends **or** if every spend in it has +waited out `SETTLE_TIMEOUT_SECONDS` (an hour). The second clause has no minimum +size. **A batch of one settles, and executes.** + +This is the standard trade in mix design, and the standard analysis of it is +Serjantov, Dingledine and Syverson, *From a Trickle to a Flood: Active Attacks on +Several Mix Batching Strategies* (Information Hiding 2002), which examines +threshold, timed, and threshold-or-timed batching and finds the disjunction +inherits the weakness of its weaker half. The argument for our case does not need +the paper, though — it follows from the code: + +- **Settlement is permissionless**, so an adversary may be the settler. They + choose the moment and the composition of every batch they send. +- A spend submitted at `t` becomes settleable **alone** at `t + 3600`, + regardless of what else is pending. +- So for any member whose spend outlives the timeout without company, an + adversary can settle it by itself, and that member's anonymity set is one. + +They do not even have to be adversarial. On a quiet pool this is simply what +happens, and nothing in the program prevents it: `k_floor` bounds a batch that +settles *by crowd* and bounds nothing about a batch that settles by clock. + +**Why it is still the right trade.** The alternative is an unconditional floor, +and an unconditional floor means a member's escrow is held hostage to the arrival +of strangers. A pool that never reaches `k_floor` again would freeze every note +in it, permanently, with no authority able to release them — the program has no +such authority by design. Given the choice between "your action may be +attributable" and "your money may be unrecoverable", this design takes the first +and says so, rather than advertising a floor it would have to break to keep +anyone solvent. + +**What a member can do about it.** The protection is traffic, not the program. +Submitting into a pool that already has spends pending is what buys a crowd; +submitting into an empty one and waiting is what does not. The tool reports the +pending count before it settles and says plainly when a batch is below the floor, +because a member who is about to settle alone should know that is what they are +doing. What the tool cannot do is manufacture other members. + +**What would fix it properly**, and is not built: a batch that fails to reach the +floor could *refund* the member rather than execute — the escape hatch would then +cost the member their action instead of their anonymity. That is a different +protocol, with a different nullifier lifecycle, and it is named here because it +is the honest answer to this section rather than left for a reader to think of. + ### Small crowds `k_floor` is enforced against notes in the tree, and a pool whose deposits are diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 48393871..15b145f8 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -516,6 +516,15 @@ pub const SETTLE_TIMEOUT_SECONDS: i64 = 3_600; /// requirement would make "synchronised" a word rather than a property. This is /// the honest middle: synchronised when there is traffic, still liquid when /// there is not. +/// +/// **The timeout side has no floor, and a batch of one settles.** Settlement is +/// permissionless, so an adversary may be the settler and may compose the batch; +/// a spend becomes settleable alone an hour after it was submitted, whatever +/// else is pending. `k_floor` bounds a batch that settles by crowd and bounds +/// nothing about one that settles by clock. That is a deliberate trade of +/// anonymity for solvency — the alternative freezes a quiet pool's escrow with +/// no authority able to release it — and `docs/THREAT_MODEL.md` argues it rather +/// than leaving it to be discovered. fn settle_epoch(program_id: &Pubkey, accounts: &[AccountInfo], count: u8) -> ProgramResult { if count == 0 { return Err(MirrorProgramError::MalformedInstruction.into()); From 4b0bc350424b13c96ae151b94723f15987c25e1c Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Tue, 28 Jul 2026 00:59:37 -0300 Subject: [PATCH 76/83] fix: a proof bound the fee but not who collects it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every field in a submit_spend travels in clear text, so a bystander watching an unlanded one could copy the proof, name themselves as the relay, and land it first. The recomputed binding was unchanged, the pairing succeeded, the nullifier burned, and the fee settled to them. Binding the amount without binding the payee is half a binding. The relay now goes into the action binding, read from the account that signed rather than from anything the caller states, so a proof is spendable only by the relay the member made it for. ACTION_DOMAIN moves to v2: the digest would differ anyway, but the version says why, and a prover left on the old shape fails as a version mismatch instead of a mystery. It was never a profitable theft, and the threat model says so rather than overstating the fix. Whoever lands the transaction funds the spend record's rent, and that record is never closed because it is the replay guard, so at the fee levels used here an attacker sank roughly twenty times what they collected. The reason to close it is that an attacker need not be paid to be effective: front-running every spend denies every relay its fee, a relay whose fee can be sniped is one nobody runs, and a pool with no relays is one where members submit from their own funded wallets. The attack never touched the cryptography — it made the honest path uneconomic and let members deanonymise themselves. The circuit is untouched. The binding is a pass-through public input — the constraint system only squares it so it is not optimised away, and the keccak happens on both sides outside — so the shape, the verifying key and the published digest are all unchanged, and vk_drift still passes. Two tests, and a fourth negative case that is the one the other three miss because the attacker alters nothing: same beneficiary, same fee, same genuine proof, and only the signer differs. It is rejected on devnet with 0x12. The replay test's comment claimed the relay was not bound and had to go; the assertion on Custom(15) survives because the nullifier guard runs before the binding is recomputed, which is now what that test pins. PROOF.md's rejection count is derived rather than written by hand. The fourth case had turned "the last two attack a live note" into a false sentence under a correct table — the failure this generator has already been bitten by once. --- README.md | 24 ++- crates/mirror-cli/src/client.rs | 1 + crates/mirror-cli/src/crowd.rs | 1 + crates/mirror-cli/src/soak.rs | 93 +++++++++- crates/mirror-core/src/hash.rs | 69 ++++++-- docs/ARCHITECTURE.md | 11 +- docs/PLAN.md | 13 +- docs/PROOF.md | 49 +++--- docs/THREAT_MODEL.md | 43 ++++- programs/mirror-pool/src/processor.rs | 13 +- programs/mirror-pool/tests/action_ceiling.rs | 6 +- programs/mirror-pool/tests/batch_ceiling.rs | 18 +- programs/mirror-pool/tests/end_to_end.rs | 169 ++++++++++++++++--- 13 files changed, 414 insertions(+), 96 deletions(-) diff --git a/README.md b/README.md index 34975114..92101a35 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Then: ``` git clone https://github.com/solanabr/mirror-pool && cd mirror-pool -make verify # fmt, clippy -D warnings, build-sbf, 258 tests +make verify # fmt, clippy -D warnings, build-sbf, 259 tests ``` Nothing in that command needs a network, an API key or an account with anybody, @@ -133,7 +133,7 @@ not say. There is a section below of things we deliberately do not claim. | `crates/mirror-provenance` | The funding-provenance measurement. | | `crates/mirror-cli` | The tool. `init-pool`, `note-new`, `deposit`, `tree`, `spend`, `settle`, `disclose`, `disclose-verify` for members; `setup`, `verify-setup`, `soak`, `crowd`, `close-table` for operators; `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection` for the measurement. | -**258 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**259 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. @@ -424,12 +424,18 @@ amount to bind; and the nullifier set is global, so an epoch boundary cannot reopen a spend. Both are pinned by tests that assert the rejection rather than assuming it. -**A relay cannot redirect or re-price an action.** The action binding is never -transmitted — it is recomputed on-chain from the selector, the target program, -the beneficiary, the relay fee, the declared account count and the payload, then -used as the third public input, so altering any of them changes the binding and -the pairing fails. The test tampers with that exact input and asserts the real -verifier rejects it. +**A relay cannot redirect, re-price, or steal an action.** The action binding is +never transmitted — it is recomputed on-chain from the selector, the target +program, the beneficiary, the relay, the relay fee, the declared account count +and the payload, then used as the third public input, so altering any of them +changes the binding and the pairing fails. The tests tamper with that exact +input and assert the real verifier rejects it. + +The relay is in there because binding a fee without binding its recipient is +half a binding: every other field travels in clear text, so a bystander watching +an unlanded `submit_spend` could otherwise lift the proof, name themselves, and +collect. Reading the relay from the signer rather than from the instruction is +what closes it. It does **not** bind *which* accounts fill an action's slots, only how many. Settlement is permissionless, so a settler chooses them; for a target whose @@ -667,7 +673,7 @@ was a memo the pool signed. The other was a **real stake delegation**: ``` Delegated Stake: 1.09771712 SOL, activating Delegated Vote Account: 2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv -Stake Authority: CWxsJdxBLm3LC6dEnBco68a6T4QNEF31N3qQRy95wN3Q ← the pool's vault +Stake Authority: EwiXhCnLcg6jEaHMumo5H4tZnVyoCtBPHU5R6hE798R5 ← the pool's vault Withdraw Authority: H9DRVAD42eiQqmXeYrX5AWwoYRr4wie4MzvJDC6Wkxmn ← the operator ``` diff --git a/crates/mirror-cli/src/client.rs b/crates/mirror-cli/src/client.rs index 7148a085..cac7ae52 100644 --- a/crates/mirror-cli/src/client.rs +++ b/crates/mirror-cli/src/client.rs @@ -382,6 +382,7 @@ pub fn spend( action.selector(), &action.target(), &beneficiary.to_bytes(), + &relay.pubkey().to_bytes(), relay_fee, action.accounts(), action.payload(), diff --git a/crates/mirror-cli/src/crowd.rs b/crates/mirror-cli/src/crowd.rs index e003af3d..db623ee2 100644 --- a/crates/mirror-cli/src/crowd.rs +++ b/crates/mirror-cli/src/crowd.rs @@ -448,6 +448,7 @@ impl Crowd { SELECTOR_INVOKE_SIGNED, &stake_program.to_bytes(), &stake_account.to_bytes(), + &relay.pubkey().to_bytes(), RELAY_FEE, stake::DELEGATE_ACCOUNTS, &stake::DELEGATE_STAKE, diff --git a/crates/mirror-cli/src/soak.rs b/crates/mirror-cli/src/soak.rs index cbd9e3bd..591af227 100644 --- a/crates/mirror-cli/src/soak.rs +++ b/crates/mirror-cli/src/soak.rs @@ -26,7 +26,7 @@ use solana_transaction::Transaction; // pool. Changing it starts a clean one, which is how the evidence run gets to // record its own `init_pool` rather than reusing a pool an earlier run created // and leaving the creation step undocumented. -const DENOMINATION: u64 = 20_000_019; // 0.02 SOL +const DENOMINATION: u64 = 20_000_023; // 0.02 SOL const ENTRY_FEE: u64 = 0; const K_FLOOR: u32 = 4; /// Members in the evidence batch. Above `K_FLOOR` so the crowd rule is satisfied @@ -341,6 +341,7 @@ impl Soak { SELECTOR_TRANSFER, &[0u8; 32], &beneficiary.to_bytes(), + &relay.pubkey().to_bytes(), RELAY_FEE, 0, &[], @@ -408,6 +409,7 @@ impl Soak { SELECTOR_INVOKE_SIGNED, &target.to_bytes(), &beneficiary.to_bytes(), + &relay.pubkey().to_bytes(), RELAY_FEE, 1, MEMO_PAYLOAD, @@ -519,6 +521,7 @@ impl Soak { SELECTOR_INVOKE_SIGNED, &stake_program.to_bytes(), &stake.to_bytes(), + &relay.pubkey().to_bytes(), RELAY_FEE, crate::stake::DELEGATE_ACCOUNTS, &crate::stake::DELEGATE_STAKE, @@ -750,6 +753,7 @@ fn proof_for_note( notes: &[Note], index: usize, beneficiary: &Pubkey, + relay: &Pubkey, relay_fee: u64, ) -> Result { use ark_std::rand::SeedableRng; @@ -758,6 +762,7 @@ fn proof_for_note( SELECTOR_TRANSFER, &[0u8; 32], &beneficiary.to_bytes(), + &relay.to_bytes(), relay_fee, 0, &[], @@ -916,7 +921,15 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re // A replayed proof: honest in every respect, against a note already spent. { let beneficiary = Keypair::new().pubkey(); - let proof = proof_for_note(&keys, &tree, ¬es, first_fresh, &beneficiary, RELAY_FEE)?; + let proof = proof_for_note( + &keys, + &tree, + ¬es, + first_fresh, + &beneficiary, + &payer.pubkey(), + RELAY_FEE, + )?; let ix = soak.spend_ix_for( proof.public_inputs[1], &proof, @@ -939,7 +952,15 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re { let member_chose = Keypair::new().pubkey(); let relay_prefers = Keypair::new().pubkey(); - let proof = proof_for_note(&keys, &tree, ¬es, spare, &member_chose, RELAY_FEE)?; + let proof = proof_for_note( + &keys, + &tree, + ¬es, + spare, + &member_chose, + &payer.pubkey(), + RELAY_FEE, + )?; let ix = soak.spend_ix_for( proof.public_inputs[1], &proof, @@ -960,7 +981,15 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re // instruction declares. { let beneficiary = Keypair::new().pubkey(); - let proof = proof_for_note(&keys, &tree, ¬es, spare, &beneficiary, RELAY_FEE)?; + let proof = proof_for_note( + &keys, + &tree, + ¬es, + spare, + &beneficiary, + &payer.pubkey(), + RELAY_FEE, + )?; let ix = soak.spend_ix_for( proof.public_inputs[1], &proof, @@ -976,6 +1005,43 @@ pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Re )?; } + // The front-run. Everything the member authorised is left intact — same + // beneficiary, same fee, same genuine proof — and the only difference is who + // submits it. + // + // This is the one attack the other three do not cover, because the attacker + // alters nothing. They lift the proof out of a transaction they saw and land + // it first under their own key. The fee is bound to the relay the member + // named, so the binding recomputed from the signer is not the one inside the + // proof, and the pairing fails before the nullifier can burn. + { + let beneficiary = Keypair::new().pubkey(); + let member_named = Keypair::new().pubkey(); + let proof = proof_for_note( + &keys, + &tree, + ¬es, + spare, + &beneficiary, + &member_named, + RELAY_FEE, + )?; + // `payer` signs, so `payer` is the relay the program sees. + let ix = soak.spend_ix_for( + proof.public_inputs[1], + &proof, + &beneficiary, + &payer.pubkey(), + RELAY_FEE, + ); + soak.expect_rejection( + "front-run relay", + "a proof is spendable only by the relay the member made it for", + ix, + &[&payer], + )?; + } + write_proof(&soak, url, out)?; println!("\nwrote {}", out.display()); Ok(()) @@ -1130,13 +1196,24 @@ fn write_proof(soak: &Soak, url: &str, out: &std::path::Path) -> Result<()> { for (name, code, proves) in &soak.negatives { md.push_str(&format!("| {name} | `{code}` | {proves} |\n")); } - md.push_str( - "\nThe last two attack a note that is still live, deposited after settlement \ + // Derived, not written by hand. Every negative but the replay case attacks a + // live note, so adding one silently turned "the last two" into a false + // sentence sitting under a correct table — the failure mode this file has + // already been bitten by once. + let live_note_cases = soak.negatives.len().saturating_sub(1); + let counted = match live_note_cases { + 1 => "The last one attacks".to_string(), + 2 => "The last two attack".to_string(), + 3 => "The last three attack".to_string(), + n => format!("The last {n} attack"), + }; + md.push_str(&format!( + "\n{counted} a note that is still live, deposited after settlement \ precisely so that they would have to. Against an already-spent note the \ replay guard fires first and the rejection would say nothing about the \ check under test — which is how a negative case comes to pass for the \ - wrong reason.\n", - ); + wrong reason.\n" + )); md.push_str("\n## Scope\n\n"); md.push_str( diff --git a/crates/mirror-core/src/hash.rs b/crates/mirror-core/src/hash.rs index b424a622..3b9d7432 100644 --- a/crates/mirror-core/src/hash.rs +++ b/crates/mirror-core/src/hash.rs @@ -85,7 +85,13 @@ pub fn nullifier(k: Field) -> Result { } /// Domain tag for the action binding preimage. -pub const ACTION_DOMAIN: &[u8] = b"mirror-pool:action:v1"; +/// +/// Bumped to `v2` when `relay` joined the preimage. The digest would differ +/// regardless — a new field guarantees that — but the version says *why* it +/// differs. A prover left on the old shape produces a binding the program does +/// not recompute, and the failure surfaces as a rejected pairing; the tag is +/// what makes that a version mismatch rather than a mystery. +pub const ACTION_DOMAIN: &[u8] = b"mirror-pool:action:v2"; /// Reduces a 256-bit digest to a canonical BN254 scalar. /// @@ -116,10 +122,28 @@ pub fn field_from_digest(digest: [u8; 32]) -> Field { /// | `selector` | run a different kind of action | /// | `target_program` | invoke a different program entirely | /// | `beneficiary` | redirect the outcome | +/// | `relay` | collect another relay's fee | /// | `relay_fee` | inflate its own cut | /// | `payload` | change the action's parameters | /// | `action_accounts` | declare a count settlement cannot satisfy | /// +/// `relay` is here because binding the fee without binding its recipient is only +/// half a binding. Every other field in this preimage travels in clear text in +/// the instruction data, so anyone watching an unlanded `submit_spend` can lift +/// the proof, substitute their own key as the relay, and land it first: the +/// digest is unchanged, the pairing succeeds, the nullifier burns, and the fee +/// settles to them. The member's payout is never at risk — `beneficiary` and the +/// amount are bound — so this is theft from the relay, not from the member. +/// +/// It is not a profitable theft. Whoever lands the transaction funds the spend +/// record's rent, and that record is never closed because it *is* the replay +/// guard, so the attacker sinks far more than any sane fee is worth. The reason +/// to close it anyway is that an attacker need not be paid to be effective: a +/// relay whose fee can be sniped at will is a relay nobody runs, and a pool with +/// no relays is one where members submit from their own funded wallets. That +/// undoes the anonymity this whole design exists to provide — not by breaking +/// the pool, but by making the honest path uneconomic. +/// /// `action_accounts` is here because leaving it out was a live griefing vector: /// it is relay-supplied, it is written verbatim into the spend record, and /// settlement refuses any spend whose declared count does not match its @@ -137,6 +161,7 @@ pub fn action_binding( selector: u64, target_program: &[u8; 32], beneficiary: &[u8; 32], + relay: &[u8; 32], relay_fee: u64, action_accounts: u8, payload: &[u8], @@ -146,6 +171,7 @@ pub fn action_binding( &selector.to_le_bytes(), target_program, beneficiary, + relay, &relay_fee.to_le_bytes(), &[action_accounts], payload, @@ -210,12 +236,13 @@ mod tests { } const PROG: [u8; 32] = [3u8; 32]; + const RELAY: [u8; 32] = [5u8; 32]; #[test] fn the_action_binding_covers_every_field_a_relay_could_change() { let bob = [7u8; 32]; let payload = b"stake 0.1".as_slice(); - let base = action_binding(1, &PROG, &bob, 5_000, 0, payload); + let base = action_binding(1, &PROG, &bob, &RELAY, 5_000, 0, payload); let mut carol = [7u8; 32]; carol[31] = 8; @@ -224,35 +251,52 @@ mod tests { assert_ne!( base, - action_binding(2, &PROG, &bob, 5_000, 0, payload), + action_binding(2, &PROG, &bob, &RELAY, 5_000, 0, payload), "selector" ); assert_ne!( base, - action_binding(1, &other_prog, &bob, 5_000, 0, payload), + action_binding(1, &other_prog, &bob, &RELAY, 5_000, 0, payload), "target program" ); assert_ne!( base, - action_binding(1, &PROG, &carol, 5_000, 0, payload), + action_binding(1, &PROG, &carol, &RELAY, 5_000, 0, payload), "beneficiary" ); assert_ne!( base, - action_binding(1, &PROG, &bob, 6_000, 0, payload), + action_binding(1, &PROG, &bob, &RELAY, 6_000, 0, payload), "relay fee" ); assert_ne!( base, - action_binding(1, &PROG, &bob, 5_000, 0, b"stake 1.0"), + action_binding(1, &PROG, &bob, &RELAY, 5_000, 0, b"stake 1.0"), "payload" ); } + /// The front-running case, stated on its own rather than as one row above. + /// + /// Every other field in the preimage is chosen by the member. This one is + /// whoever signs, so it is the only field an attacker can change *without* + /// altering what the member asked for — which is exactly why leaving it out + /// made the proof transferable to a different payee at no cost. + #[test] + fn two_relays_carrying_the_same_action_do_not_share_a_binding() { + let bob = [7u8; 32]; + let thief = [6u8; 32]; + assert_ne!( + action_binding(1, &PROG, &bob, &RELAY, 5_000, 0, b"stake 0.1"), + action_binding(1, &PROG, &bob, &thief, 5_000, 0, b"stake 0.1"), + "a proof must not verify for a relay the member did not name" + ); + } + #[test] fn the_binding_is_deterministic_and_lands_in_the_field() { - let a = action_binding(1, &PROG, &[9u8; 32], 1, 0, b"x"); - let b = action_binding(1, &PROG, &[9u8; 32], 1, 0, b"x"); + let a = action_binding(1, &PROG, &[9u8; 32], &RELAY, 1, 0, b"x"); + let b = action_binding(1, &PROG, &[9u8; 32], &RELAY, 1, 0, b"x"); assert_eq!(a, b); // Canonical by construction: the top byte is cleared. assert_eq!(a.to_bytes()[0], 0); @@ -262,8 +306,8 @@ mod tests { #[test] fn an_empty_payload_is_a_distinct_action_from_a_zero_byte_one() { assert_ne!( - action_binding(1, &PROG, &[1u8; 32], 0, 0, b""), - action_binding(1, &PROG, &[1u8; 32], 0, 0, b"\0"), + action_binding(1, &PROG, &[1u8; 32], &RELAY, 0, 0, b""), + action_binding(1, &PROG, &[1u8; 32], &RELAY, 0, 0, b"\0"), ); } @@ -282,12 +326,13 @@ mod tests { fn the_domain_tag_separates_bindings_from_raw_hashes() { // Without the tag, a preimage assembled elsewhere could collide with a // binding. With it, an attacker must also control the tag. - let with_tag = action_binding(0, &[0u8; 32], &[0u8; 32], 0, 0, b""); + let with_tag = action_binding(0, &[0u8; 32], &[0u8; 32], &[0u8; 32], 0, 0, b""); let raw = field_from_digest( solana_keccak_hasher::hashv(&[ &0u64.to_le_bytes(), &[0u8; 32], &[0u8; 32], + &[0u8; 32], &0u64.to_le_bytes(), ]) .to_bytes(), diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 442c69c1..de909c65 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -58,7 +58,7 @@ itself a domain separator and a free one: The action binding is not in this table. Its payload is variable length and Poseidon is a fixed-arity compression, so the binding is a keccak digest under -the domain tag `mirror-pool:action:v1`, masked into the field. +the domain tag `mirror-pool:action:v2`, masked into the field. An integer tag was the first design and it was wrong: with a small tag constant, a Merkle node whose left child equals the tag collides with a nullifier. @@ -153,11 +153,16 @@ transaction history. records the authorised action. Pays out nothing. The action binding is never transmitted. It is recomputed on-chain from the -selector, the target program, the beneficiary, the relay fee, the declared -account count and the payload, and used as the third public input, +selector, the target program, the beneficiary, the relay, the relay fee, the +declared account count and the payload, and used as the third public input, so a relay that alters any of them produces a different binding and the pairing fails. There is no separate field that could be checked incorrectly. +The relay is taken from the account that signed, never from anything the caller +states. A proof is therefore spendable only by the relay the member made it for, +which is what stops a bystander from lifting it out of an unlanded transaction +and landing it first under their own key. + The relay signs, never the member. A member paying their own fee would sign with their own wallet and destroy their own anonymity, so no member key appears on chain on this path. diff --git a/docs/PLAN.md b/docs/PLAN.md index 5d54449b..d61bd70b 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -101,8 +101,9 @@ Any further value we need to commit to gets folded into `action_binding` rather than added as a fourth input. See `GROTH16_INTEGRATION.md` for the verified conversion path and its pitfalls. -The action binding covers the action selector, its parameters, the beneficiary -and the relay fee, so every economically meaningful field is inside the proof. +The action binding covers the action selector, its parameters, the beneficiary, +the relay and the relay fee, so every economically meaningful field is inside +the proof. ### Two-phase epochs — synchronization without a CU explosion @@ -137,6 +138,14 @@ Two rules follow, and both are easy to lose by accident: seeds is the tempting shortcut, and it is a trap: seeds cannot be changed, so without a rotation instruction the authority is permanent and its loss is terminal. + + > **Shipped narrower, and deliberately:** any key may relay, but a given proof + > names the relay it was made for, because the relay is part of the action + > binding. The property this rule was protecting — no authority whose absence + > freezes the pool — is untouched: there is still no allowlist and no + > registration. What changed is that the member chooses their relay instead of + > the choice going to whoever lands the transaction first. `THREAT_MODEL.md` + > has the attack that forced it. - **There is always an exit.** A member may always self-spend, paying their own fee and accepting the privacy loss, so funds are never hostage to a relay's liveness. diff --git a/docs/PROOF.md b/docs/PROOF.md index 5fb47aec..9633437c 100644 --- a/docs/PROOF.md +++ b/docs/PROOF.md @@ -4,27 +4,27 @@ Every line here is a transaction that landed. Nothing is simulated: the proof is - cluster: `devnet` - program: `8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa` -- pool: `5R5mrXeginkRYwv5NmXcSVVLpUW2obuc7YwdEBt31x5n` -- vault: `CWxsJdxBLm3LC6dEnBco68a6T4QNEF31N3qQRy95wN3Q` +- pool: `JBvD5u5foKCThTfSx1TozGHGCcfWu5gDNy1u2FphP51q` +- vault: `EwiXhCnLcg6jEaHMumo5H4tZnVyoCtBPHU5R6hE798R5` ## Flows | step | signature | note | |---|---|---| -| init_pool | [`5x5wbVjg1g43KMCZQ8weY5eEjxfLizdjAiSvHVX7kA3bc1yRkKm4pAwWquzoCwwWzkxxaiTfAGoXyT2qi5n129hD`](https://explorer.solana.com/tx/5x5wbVjg1g43KMCZQ8weY5eEjxfLizdjAiSvHVX7kA3bc1yRkKm4pAwWquzoCwwWzkxxaiTfAGoXyT2qi5n129hD?cluster=devnet) | denomination 20000019, k_floor 4 | -| deposit | [`5NWeHxd2QLKskpPTPQhJJk5vuav8VojN1vw8PKwz8F3P2xXfjxfaAnScM57SqVRXv2HTNNsKpMdbZJCFVt63ppmZ`](https://explorer.solana.com/tx/5NWeHxd2QLKskpPTPQhJJk5vuav8VojN1vw8PKwz8F3P2xXfjxfaAnScM57SqVRXv2HTNNsKpMdbZJCFVt63ppmZ?cluster=devnet) | note 1 | -| deposit | [`2vNqo1GEEmRBpPCUUh8kiXuDUMz5ZkKNmJHKJfGPaHdW8jgDS9f3g3dTNUJsdEXtoRUXmSGCH8G7XURYqus3EBho`](https://explorer.solana.com/tx/2vNqo1GEEmRBpPCUUh8kiXuDUMz5ZkKNmJHKJfGPaHdW8jgDS9f3g3dTNUJsdEXtoRUXmSGCH8G7XURYqus3EBho?cluster=devnet) | note 2 | -| deposit | [`2KF8noWF6B4FCwDvrfGMwZkZoR7TR8ebDXwmAw4wCdFDxFQA9A5z2gsFjDDJm8YsC2pX7AQvsRPxjcLGCCa54wSg`](https://explorer.solana.com/tx/2KF8noWF6B4FCwDvrfGMwZkZoR7TR8ebDXwmAw4wCdFDxFQA9A5z2gsFjDDJm8YsC2pX7AQvsRPxjcLGCCa54wSg?cluster=devnet) | note 3 | -| deposit | [`2p8CVgPFQ1n36HTJAqebTNeRVxX958cKxnD1Xce3UhExd35DY7XLFDCMDT4mtKUsMKXStmaTuyDyiK93ZPqtQxqj`](https://explorer.solana.com/tx/2p8CVgPFQ1n36HTJAqebTNeRVxX958cKxnD1Xce3UhExd35DY7XLFDCMDT4mtKUsMKXStmaTuyDyiK93ZPqtQxqj?cluster=devnet) | note 4 | -| deposit | [`5udQNeabeCtLQ6KP3Kb4H1bmbiBfvLKfWsJ3Bg7oUivF4Kjqcn2Z3e8fh8mwYWE7PdcgLA3hJts8zyLM9xkgUd8K`](https://explorer.solana.com/tx/5udQNeabeCtLQ6KP3Kb4H1bmbiBfvLKfWsJ3Bg7oUivF4Kjqcn2Z3e8fh8mwYWE7PdcgLA3hJts8zyLM9xkgUd8K?cluster=devnet) | note 5 | -| create stake account | [`JWDRNZEWW78JRty7ARo8rJ3e4NtwKb4tdGkqBHzBSH52ruM1AA1oPQ31KBvhJTQjh4RXLxRFpVnmGhQfZR8q67E`](https://explorer.solana.com/tx/JWDRNZEWW78JRty7ARo8rJ3e4NtwKb4tdGkqBHzBSH52ruM1AA1oPQ31KBvhJTQjh4RXLxRFpVnmGhQfZR8q67E?cluster=devnet) | 1100000000 lamports, staker = the pool's vault, withdrawer = the operator | -| submit_spend | [`Ci9zMYy96rPowdJCWne1MVVb2p7jhcro3paKt6wJrvCsHZqziNKGCiiQpjV9Th9y7YLmnxRJcdLsJkviz7ztGXM`](https://explorer.solana.com/tx/Ci9zMYy96rPowdJCWne1MVVb2p7jhcro3paKt6wJrvCsHZqziNKGCiiQpjV9Th9y7YLmnxRJcdLsJkviz7ztGXM?cluster=devnet) | note 0, relay-signed | -| submit_spend | [`4nDV7VmAZHnLEEBNm2hgAX8yf4A59KL7NvMsnVpBevGtFKcadHGKPYRcPLK5XH4n29wTP8HUciCgYLKBUEqmYgYg`](https://explorer.solana.com/tx/4nDV7VmAZHnLEEBNm2hgAX8yf4A59KL7NvMsnVpBevGtFKcadHGKPYRcPLK5XH4n29wTP8HUciCgYLKBUEqmYgYg?cluster=devnet) | note 1, relay-signed | -| submit_spend | [`5uah1udmiCW1VHNiUi9SwqEwLYSMEP49WrxWzE178wpMHD1PFTjHiX8RuphKBtohQnVaThKJeEXkHtyxsPzT43C8`](https://explorer.solana.com/tx/5uah1udmiCW1VHNiUi9SwqEwLYSMEP49WrxWzE178wpMHD1PFTjHiX8RuphKBtohQnVaThKJeEXkHtyxsPzT43C8?cluster=devnet) | note 2, relay-signed | -| submit_spend | [`mPaXDDAQmMjCJmowSRwsJK2y4S38Y2NF9FAH4EniZJGcVtjomifSgJX5gaJqxjpS7XdKtvvQfQ6CJB3hX9PXp4q`](https://explorer.solana.com/tx/mPaXDDAQmMjCJmowSRwsJK2y4S38Y2NF9FAH4EniZJGcVtjomifSgJX5gaJqxjpS7XdKtvvQfQ6CJB3hX9PXp4q?cluster=devnet) | note 3, relay-signed, action: pool-signed CPI to SPL Memo | -| submit_spend | [`53ZdkNqVp7APhPKRQKVnUiKsCxKAKbaUrEK54EFSWuC3g99JzLqryzES6FM2TubdkgdY9sPVP9trSmcNVeHy9hBW`](https://explorer.solana.com/tx/53ZdkNqVp7APhPKRQKVnUiKsCxKAKbaUrEK54EFSWuC3g99JzLqryzES6FM2TubdkgdY9sPVP9trSmcNVeHy9hBW?cluster=devnet) | note 4, relay-signed, action: pool-signed stake delegation | -| settle_epoch | [`5VpoicNHu6m7YUvUSX7x2sztRzc751rmMRMg5SBR6qLKDxv5tFYG5pJmqCrDJajpD2sR8dCBd9BXqgooLw86iNqN`](https://explorer.solana.com/tx/5VpoicNHu6m7YUvUSX7x2sztRzc751rmMRMg5SBR6qLKDxv5tFYG5pJmqCrDJajpD2sR8dCBd9BXqgooLw86iNqN?cluster=devnet) | 5 spends in one transaction, 2 of them a CPI the pool signed | -| deposit | [`44bFc7PBkwMAMamkKVfMPWhiBHm7cRbCcJwfZed78Q2z2SyVrHzPXswWzoDAjf9wufGveEvTougb4f1E4DT7yWzr`](https://explorer.solana.com/tx/44bFc7PBkwMAMamkKVfMPWhiBHm7cRbCcJwfZed78Q2z2SyVrHzPXswWzoDAjf9wufGveEvTougb4f1E4DT7yWzr?cluster=devnet) | note 6 | +| init_pool | [`4Ru5RVJ1pyoFSYKrf58Rj43LsawpHQymEwW9zQgKNPpGuv5MdSBzT1Fyxpo8dtdXmcCxCZZN2SwEZVvnkSQXM5dn`](https://explorer.solana.com/tx/4Ru5RVJ1pyoFSYKrf58Rj43LsawpHQymEwW9zQgKNPpGuv5MdSBzT1Fyxpo8dtdXmcCxCZZN2SwEZVvnkSQXM5dn?cluster=devnet) | denomination 20000023, k_floor 4 | +| deposit | [`2z4zzRhA98HJcMHCMF9wzqYMjYCbBNX8K52ASWjhcFAQ887hCzHd1woURxLt2REgsTsLRMn32NubEiXqpGRTCHhZ`](https://explorer.solana.com/tx/2z4zzRhA98HJcMHCMF9wzqYMjYCbBNX8K52ASWjhcFAQ887hCzHd1woURxLt2REgsTsLRMn32NubEiXqpGRTCHhZ?cluster=devnet) | note 1 | +| deposit | [`45uwVLTFs81kU5rv4LgKHWS4apiDoThVRMrq8LziKr5iX9papXySRC3S1fXumZ8UEn4otMzG6iyT6LhcgRSYxu5d`](https://explorer.solana.com/tx/45uwVLTFs81kU5rv4LgKHWS4apiDoThVRMrq8LziKr5iX9papXySRC3S1fXumZ8UEn4otMzG6iyT6LhcgRSYxu5d?cluster=devnet) | note 2 | +| deposit | [`4QaHJBxGD1St4hiLyqmrmJmztTbmam1ssGrSABFE3S6sM6Q4JfSY1oqG5onXty17RT7KNaARXjTM2HYJMsyvur6D`](https://explorer.solana.com/tx/4QaHJBxGD1St4hiLyqmrmJmztTbmam1ssGrSABFE3S6sM6Q4JfSY1oqG5onXty17RT7KNaARXjTM2HYJMsyvur6D?cluster=devnet) | note 3 | +| deposit | [`5ZtoW7nNqVwSYJuzmQqwBiEm52tRgcQdYYGLbGMmD8cN1cqtbqh3mdjRqQzKBqWeetnnoQhTHBaUebTq8SxmBovW`](https://explorer.solana.com/tx/5ZtoW7nNqVwSYJuzmQqwBiEm52tRgcQdYYGLbGMmD8cN1cqtbqh3mdjRqQzKBqWeetnnoQhTHBaUebTq8SxmBovW?cluster=devnet) | note 4 | +| deposit | [`5TdTHdRJXddC1JP6uW4C993LY7ifSoi4ZxbavwhK95Wq96MyGUmHuQm88tkRRykhz4pzpaCQhJC3TR2A7Y843jxB`](https://explorer.solana.com/tx/5TdTHdRJXddC1JP6uW4C993LY7ifSoi4ZxbavwhK95Wq96MyGUmHuQm88tkRRykhz4pzpaCQhJC3TR2A7Y843jxB?cluster=devnet) | note 5 | +| create stake account | [`WmqHSUXx71nm81RzbhjgRZir1hzTMzGxg6k45fgPCi8cbyt1D3FVR5DKAETSapCmeQojM1TzMgAGbotReLBW4Kz`](https://explorer.solana.com/tx/WmqHSUXx71nm81RzbhjgRZir1hzTMzGxg6k45fgPCi8cbyt1D3FVR5DKAETSapCmeQojM1TzMgAGbotReLBW4Kz?cluster=devnet) | 1100000000 lamports, staker = the pool's vault, withdrawer = the operator | +| submit_spend | [`42bLZCNNwSo82r3Zwq1VVB4PqQCFm5ETjiFBai9QZYjNvzrkeJBwiYWb47RRMyXJ3Q9w22yV88GdSUFZ2CRKfGMK`](https://explorer.solana.com/tx/42bLZCNNwSo82r3Zwq1VVB4PqQCFm5ETjiFBai9QZYjNvzrkeJBwiYWb47RRMyXJ3Q9w22yV88GdSUFZ2CRKfGMK?cluster=devnet) | note 0, relay-signed | +| submit_spend | [`4jJwmRta9miD3yQgvhWSzZysZTS5wsKPM4E1ZPnHMnUCa3pPxXew4DQQAG3Y7Luk1bECD9V3A39dSBDrXxY3Wegw`](https://explorer.solana.com/tx/4jJwmRta9miD3yQgvhWSzZysZTS5wsKPM4E1ZPnHMnUCa3pPxXew4DQQAG3Y7Luk1bECD9V3A39dSBDrXxY3Wegw?cluster=devnet) | note 1, relay-signed | +| submit_spend | [`4fpZU88zDAeu2ySvjdit7My1FTzp9P7MZbRHHzUDD3V53nBLMruw5vb1WPG2zmGUnt9AFD1j6uimGmzKsYiLDF8A`](https://explorer.solana.com/tx/4fpZU88zDAeu2ySvjdit7My1FTzp9P7MZbRHHzUDD3V53nBLMruw5vb1WPG2zmGUnt9AFD1j6uimGmzKsYiLDF8A?cluster=devnet) | note 2, relay-signed | +| submit_spend | [`3gEhEPza2ryHiJuxPo8fZktqyhwyh2fwCwx18Bw7q95NPrZaszqpLtZq6KfrXefD7RR5eSbiR4Di5etv6HAWGaYL`](https://explorer.solana.com/tx/3gEhEPza2ryHiJuxPo8fZktqyhwyh2fwCwx18Bw7q95NPrZaszqpLtZq6KfrXefD7RR5eSbiR4Di5etv6HAWGaYL?cluster=devnet) | note 3, relay-signed, action: pool-signed CPI to SPL Memo | +| submit_spend | [`XLZJ3wDgJXe9YhiVD2akpEKHhwMziExkMntUtbW86TkKn6ktw5hWNrmqYG7FZ51jiYuGr6PhnE87v4kvEacViBA`](https://explorer.solana.com/tx/XLZJ3wDgJXe9YhiVD2akpEKHhwMziExkMntUtbW86TkKn6ktw5hWNrmqYG7FZ51jiYuGr6PhnE87v4kvEacViBA?cluster=devnet) | note 4, relay-signed, action: pool-signed stake delegation | +| settle_epoch | [`4T6MYo8rMW6wYZb6FdKSQUSrinLGFoo6gpq5YMQ4RAqCnFbHdeSueeMosEkoBf5uYEhcQRtiCJA16BtvKAKZ58km`](https://explorer.solana.com/tx/4T6MYo8rMW6wYZb6FdKSQUSrinLGFoo6gpq5YMQ4RAqCnFbHdeSueeMosEkoBf5uYEhcQRtiCJA16BtvKAKZ58km?cluster=devnet) | 5 spends in one transaction, 2 of them a CPI the pool signed | +| deposit | [`2jXf1wQ3J3And4TjAS47oUggAzwGFc2wVGy4v4xDyBeoVopNfa6RdW4SCBrNcva3ayjQQcFSZLdRtA8FJockosZ5`](https://explorer.solana.com/tx/2jXf1wQ3J3And4TjAS47oUggAzwGFc2wVGy4v4xDyBeoVopNfa6RdW4SCBrNcva3ayjQQcFSZLdRtA8FJockosZ5?cluster=devnet) | note 6 | ## Vault accounting @@ -32,14 +32,14 @@ The accounting invariant is a statement about the vault's lamports, so here are | quantity | lamports | |---|---| -| denomination | 20000019 | +| denomination | 20000023 | | notes settled | 5 | | relay fee (taken out of the denomination, not added) | 200000 | -| vault before settlement | 100890975 | +| vault before settlement | 100890995 | | vault after settlement | 890880 | | rent-exempt minimum, 0 bytes | 890880 | -| **paid out** | **100000095** | -| **owed** (denomination × notes) | **100000095** | +| **paid out** | **100000115** | +| **owed** (denomination × notes) | **100000115** | Paid out equals owed, and the vault came to rest on its floor with a remainder of 0 lamports. The soak asserts both and fails the run otherwise, so this table cannot record a discrepancy and still exit successfully. @@ -50,14 +50,14 @@ The settlement above carried 5 spends, and 2 of them were not a transfer: the po A signature only proves the transaction landed. It says nothing about who signed the instruction the pool made *inside* it, so the evidence has to come from the callee. SPL Memo refuses any account handed to it that has not signed, and names the ones that did: ``` -Program log: Signed by CWxsJdxBLm3LC6dEnBco68a6T4QNEF31N3qQRy95wN3Q +Program log: Signed by EwiXhCnLcg6jEaHMumo5H4tZnVyoCtBPHU5R6hE798R5 ``` -That is the pool's vault, `CWxsJdxBLm3LC6dEnBco68a6T4QNEF31N3qQRy95wN3Q`, which has no private key — it signed through seeds only the program holds. The soak reads this line back from the cluster and fails the run if it is absent, so this section cannot appear without the callee having said it. +That is the pool's vault, `EwiXhCnLcg6jEaHMumo5H4tZnVyoCtBPHU5R6hE798R5`, which has no private key — it signed through seeds only the program holds. The soak reads this line back from the cluster and fails the run if it is absent, so this section cannot appear without the callee having said it. ## The pool delegated stake, as a member's authority -The same settlement carried a second signed action, and this one is the case the design exists for: a **real stake delegation**. Stake account [`53hDajhaYAvLZnazS4e86huMN8Uba8X6nAGNpAE9dL7R`](https://explorer.solana.com/address/53hDajhaYAvLZnazS4e86huMN8Uba8X6nAGNpAE9dL7R?cluster=devnet) is now delegated to validator [`2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv`](https://explorer.solana.com/address/2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv?cluster=devnet). +The same settlement carried a second signed action, and this one is the case the design exists for: a **real stake delegation**. Stake account [`CMMwt1SgrJyEfdBsxxUNSwsPwNiU94ZNkVGMUzvMU1Ri`](https://explorer.solana.com/address/CMMwt1SgrJyEfdBsxxUNSwsPwNiU94ZNkVGMUzvMU1Ri?cluster=devnet) is now delegated to validator [`2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv`](https://explorer.solana.com/address/2f9C9AU8nFRKUub8NHToNiZzcwmYiNeipVuP8akKgRVv?cluster=devnet). `DelegateStake` requires the **staker authority** to sign. No member can be that authority without appearing on chain and undoing the point, so the pool is, and the pool signed. The account state is read back after settlement: a stake account only reaches the `Stake` variant by being delegated — an initialised but undelegated one is a different variant — so the check distinguishes "the instruction landed" from "the delegation took", and the validator's key is read out of the account rather than assumed from what was requested. @@ -74,8 +74,9 @@ A negative case is only evidence if the program's own error code is what rejecte | replayed nullifier | `0xf` | a nullifier is spent once, ever -- not once per epoch | | redirected beneficiary | `0x12` | a relay cannot send a member's payout somewhere the member did not choose | | inflated relay fee | `0x12` | a relay cannot re-price the work after the member authorised it | +| front-run relay | `0x12` | a proof is spendable only by the relay the member made it for | -The last two attack a note that is still live, deposited after settlement precisely so that they would have to. Against an already-spent note the replay guard fires first and the rejection would say nothing about the check under test — which is how a negative case comes to pass for the wrong reason. +The last three attack a note that is still live, deposited after settlement precisely so that they would have to. Against an already-spent note the replay guard fires first and the rejection would say nothing about the check under test — which is how a negative case comes to pass for the wrong reason. ## Scope diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index 2757f602..f6f71e48 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -26,9 +26,13 @@ pool's notes. settler-signed. A member's wallet never touches the protocol after depositing. **A relay cannot alter what was authorised.** The action binding covers the -selector, the target program, the beneficiary, the relay fee, the declared -account count and the payload, under a domain tag, and is recomputed on-chain -rather than transmitted. +selector, the target program, the beneficiary, the relay, the relay fee, the +declared account count and the payload, under a domain tag, and is recomputed +on-chain rather than transmitted. + +**A proof is spendable only by the relay it was made for.** The relay in that +binding is read from the account that signed, so a proof cannot be carried by a +key the member did not name. **Actions carry one caller.** Every action is invoked by the pool program on a member's behalf and funded out of the pool's vault, so the on-chain trace of a @@ -212,6 +216,39 @@ clause is a discipline the member keeps, not a property the program enforces, an a threat model that omits it is describing a smaller adversary than the one that exists. +### The relay market is attackable even though the relay's fee is not + +Everything in a `submit_spend` travels in clear text, which the section above +treats as a disclosure problem. It is also a transferability problem, and that +half is worth stating on its own because the fix is recent and the reasoning +behind it is easy to get backwards. + +A bystander watching an unlanded `submit_spend` can copy the proof and every +field around it. Until the relay joined the action binding, they could name +themselves as the relay and land it first: the recomputed binding was unchanged, +the pairing succeeded, and the fee settled to them. The member's payout was never +reachable — the beneficiary and the amount have always been bound — so this was +theft from the relay, not from the member. + +**It was never a profitable theft, and that is the part worth being precise +about.** Whoever lands the transaction funds the spend record's rent, and that +record is never closed, because it *is* the replay guard. At the fee levels this +repository has actually used, an attacker sank roughly twenty times what they +collected. Anyone presenting this as a drain would be overstating it. + +The reason to close it anyway is that **an attacker does not need to be paid to +be effective**. Front-running every spend costs them rent each time and denies +every relay their fee. A relay whose fee can be sniped at will is a relay nobody +runs, and a pool with no relays is a pool where members submit from their own +funded wallets — which is precisely the disclosure the section above calls the +sharpest one available. The attack never touched the cryptography. It made the +honest path uneconomic and let the members deanonymise themselves. + +So the relay is now read from the account that signed and folded into the +binding, and a proof is spendable only by the relay it was made for. What remains +open is unchanged and stated where it belongs: a member who relays for themselves +still names themselves, and no binding can prevent that. + ### A large settlement publishes its participant list early A batch that outgrows the 1232-byte packet settles through an address lookup diff --git a/programs/mirror-pool/src/processor.rs b/programs/mirror-pool/src/processor.rs index 15b145f8..4135455f 100644 --- a/programs/mirror-pool/src/processor.rs +++ b/programs/mirror-pool/src/processor.rs @@ -288,8 +288,10 @@ fn deposit(program_id: &Pubkey, accounts: &[AccountInfo], commitment: [u8; 32]) /// /// The relay signs, not the member. A member who pays their own fee signs with /// their own wallet and destroys their own anonymity, so no member key appears -/// on chain at any point in this path. Relaying is permissionless: any key may -/// do it, and there is no authority whose absence freezes the pool. +/// on chain at any point in this path. Relaying stays permissionless — there is +/// no allowlist and no authority whose absence freezes the pool — but a given +/// proof names the relay it was made for, so the choice is the member's rather +/// than the winner of a race. /// /// Note the ordering. The proof is verified *before* the spend account is /// created, but the account's existence is what makes replay impossible, and @@ -377,6 +379,12 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest // beneficiary or its own fee, this binding differs from the one the prover // committed to and the pairing fails. There is no separate field that could // be checked incorrectly or forgotten. + // + // The relay's own key goes in too, taken from the account that signed rather + // than from anything the caller could state. That is what stops a bystander + // from lifting a proof out of an unlanded transaction, naming themselves as + // relay, and landing it first to collect the fee: the binding they would + // need is the member's, and the member never signed one naming them. if req.payload.len() > MAX_PAYLOAD { return Err(MirrorProgramError::PayloadTooLarge.into()); } @@ -397,6 +405,7 @@ fn submit_spend(program_id: &Pubkey, accounts: &[AccountInfo], req: SpendRequest req.selector, &req.target_program, &req.beneficiary, + &relay.key.to_bytes(), req.relay_fee, req.action_accounts, &req.payload, diff --git a/programs/mirror-pool/tests/action_ceiling.rs b/programs/mirror-pool/tests/action_ceiling.rs index 982506ac..85afbe52 100644 --- a/programs/mirror-pool/tests/action_ceiling.rs +++ b/programs/mirror-pool/tests/action_ceiling.rs @@ -601,11 +601,15 @@ fn prove_delegations(keys: &Keys, tree: &MerkleTree, notes: &[Note]) -> Vec Vec usize { /// A member's proof and the two addresses it was bound to. /// -/// The binding covers the selector, the target, the beneficiary, the relay fee -/// and the payload — not the pool and not the relay's identity. So one round of -/// proving can be replayed into a second, identical pool, which is what lets this -/// file run two independent settlement experiments for the price of one. +/// The binding covers the selector, the target, the beneficiary, the relay, the +/// relay fee and the payload — but *not* the pool. So one round of proving can be +/// replayed into a second, identical pool, which is what lets this file run two +/// independent settlement experiments for the price of one. +/// +/// That the relay is bound is why it is carried here rather than minted at +/// submission time: both runs must present the same relay, because a second +/// relay would need a second proof. struct Ticket { proof: SolanaProof, beneficiary: Pubkey, @@ -312,11 +316,15 @@ fn prove_tickets(keys: &Keys, tree: &MerkleTree, notes: &[Note]) -> Vec .enumerate() .map(|(index, note)| { let beneficiary = Pubkey::new_unique(); + // Minted before the binding, not after: the relay is part of the + // preimage now, so the proof has to know which key will carry it. + let relay = Keypair::new(); let merkle_proof = tree.proof(index as u64).unwrap(); let binding = mirror_core::action_binding( SELECTOR, &[0u8; 32], &beneficiary.to_bytes(), + &relay.pubkey().to_bytes(), RELAY_FEE, 0, &[], @@ -331,7 +339,7 @@ fn prove_tickets(keys: &Keys, tree: &MerkleTree, notes: &[Note]) -> Vec Ticket { proof: prove(keys, &witness, &mut rng).expect("proving"), beneficiary, - relay: Keypair::new(), + relay, } }) .collect() diff --git a/programs/mirror-pool/tests/end_to_end.rs b/programs/mirror-pool/tests/end_to_end.rs index cf794a48..6a7fd32a 100644 --- a/programs/mirror-pool/tests/end_to_end.rs +++ b/programs/mirror-pool/tests/end_to_end.rs @@ -254,6 +254,7 @@ fn spend_ix_at_fee( SELECTOR, &[0u8; 32], &beneficiary.to_bytes(), + &relay.to_bytes(), relay_fee, 0, &[], @@ -300,6 +301,7 @@ fn proof_for( notes: &[Note], index: usize, beneficiary: &Pubkey, + relay: &Pubkey, ) -> mirror_circuit::SolanaProof { use ark_std::rand::SeedableRng; let merkle_proof = tree.proof(index as u64).unwrap(); @@ -307,6 +309,7 @@ fn proof_for( SELECTOR, &[0u8; 32], &beneficiary.to_bytes(), + &relay.to_bytes(), RELAY_FEE, 0, &[], @@ -370,7 +373,7 @@ fn a_real_proof_verifies_on_chain_and_records_the_spend() { let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); - let proof = proof_for(&keys, &tree, ¬es, 2, &beneficiary); + let proof = proof_for(&keys, &tree, ¬es, 2, &beneficiary, &relay.pubkey()); let nullifier = proof.public_inputs[1]; let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &relay.pubkey()); @@ -428,7 +431,7 @@ fn the_same_note_cannot_be_spent_twice() { .airdrop(&second_relay.pubkey(), 10_000_000_000) .unwrap(); - let proof = proof_for(&keys, &tree, ¬es, 1, &beneficiary); + let proof = proof_for(&keys, &tree, ¬es, 1, &beneficiary, &first_relay.pubkey()); let nullifier = proof.public_inputs[1]; let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &first_relay.pubkey()); @@ -436,11 +439,17 @@ fn the_same_note_cannot_be_spent_twice() { // The replay goes through a *different* relay, so the transaction is not // byte-identical and the runtime's duplicate-signature check cannot be what - // rejects it. The relay identity is not part of the action binding, so the - // proof itself is still entirely valid — the only thing standing in the way - // is the nullifier record. An earlier version of this test reused the first - // relay and passed for the wrong reason: it was rejected as AlreadyProcessed - // before the program ran at all. + // rejects it. An earlier version of this test reused the first relay and + // passed for the wrong reason: it was rejected as AlreadyProcessed before + // the program ran at all. + // + // Since the relay joined the action binding, this proof would *also* fail + // the pairing under the second relay — which is exactly why the assertion + // below is on `Custom(15)` and not merely on "rejected". The nullifier guard + // is checked before the binding is recomputed, so a spent note costs a + // replayer ~11k CU instead of the ~95k a pairing would. Getting + // ProofVerificationFailed here would mean that ordering had silently + // inverted, and the cheap guard had stopped being the first thing to run. let replay = spend_ix( &env, &proof, @@ -469,7 +478,14 @@ fn a_relay_cannot_redirect_the_payout() { let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); - let proof = proof_for(&keys, &tree, ¬es, 0, &honest_beneficiary); + let proof = proof_for( + &keys, + &tree, + ¬es, + 0, + &honest_beneficiary, + &relay.pubkey(), + ); let nullifier = proof.public_inputs[1]; // The relay swaps in its own address after the member proved. @@ -492,7 +508,7 @@ fn a_relay_cannot_inflate_its_own_fee() { let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); - let proof = proof_for(&keys, &tree, ¬es, 3, &beneficiary); + let proof = proof_for(&keys, &tree, ¬es, 3, &beneficiary, &relay.pubkey()); let nullifier = proof.public_inputs[1]; let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); @@ -536,7 +552,7 @@ fn a_proof_against_an_unknown_root_is_rejected() { let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); - let mut proof = proof_for(&keys, &tree, ¬es, 4, &beneficiary); + let mut proof = proof_for(&keys, &tree, ¬es, 4, &beneficiary, &relay.pubkey()); let nullifier = proof.public_inputs[1]; // A root the pool has never held. Flip a low bit so the value stays a // canonical scalar and the rejection comes from the history check. @@ -559,7 +575,7 @@ fn a_pool_below_its_anonymity_floor_refuses_to_act() { let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); - let proof = proof_for(&keys, &tree, ¬es, 0, &beneficiary); + let proof = proof_for(&keys, &tree, ¬es, 0, &beneficiary, &relay.pubkey()); let nullifier = proof.public_inputs[1]; let ix = spend_ix(&env, &proof, nullifier, &beneficiary, &relay.pubkey()); let err = env @@ -579,7 +595,7 @@ fn a_forged_proof_is_rejected() { let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); - let mut proof = proof_for(&keys, &tree, ¬es, 2, &beneficiary); + let mut proof = proof_for(&keys, &tree, ¬es, 2, &beneficiary, &relay.pubkey()); let nullifier = proof.public_inputs[1]; // Corrupt the proof itself. proof.proof_a[63] ^= 1; @@ -612,7 +628,7 @@ fn submit_batch( let beneficiary = Pubkey::new_unique(); let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); - let proof = proof_for(keys, tree, notes, i, &beneficiary); + let proof = proof_for(keys, tree, notes, i, &beneficiary, &relay.pubkey()); let nullifier = proof.public_inputs[1]; let ix = spend_ix(env, &proof, nullifier, &beneficiary, &relay.pubkey()); env.send(ix, &relay) @@ -871,8 +887,15 @@ fn a_member_can_always_exit_without_any_relay() { let beneficiary = member.pubkey(); let merkle_proof = tree.proof(0).unwrap(); - let binding = - mirror_core::action_binding(SELECTOR, &[0u8; 32], &beneficiary.to_bytes(), 0, 0, &[]); + let binding = mirror_core::action_binding( + SELECTOR, + &[0u8; 32], + &beneficiary.to_bytes(), + &member.pubkey().to_bytes(), + 0, + 0, + &[], + ); let witness = Witness { note: notes[0], merkle_proof: &merkle_proof, @@ -1037,6 +1060,7 @@ fn action_ix_sel( ) } +#[allow(clippy::too_many_arguments)] fn action_proof( keys: &Keys, tree: &MerkleTree, @@ -1044,9 +1068,20 @@ fn action_proof( index: usize, target: &Pubkey, beneficiary: &Pubkey, + relay: &Pubkey, payload: &[u8], ) -> mirror_circuit::SolanaProof { - action_proof_n(keys, tree, notes, index, target, beneficiary, 0, payload) + action_proof_n( + keys, + tree, + notes, + index, + target, + beneficiary, + relay, + 0, + payload, + ) } #[allow(clippy::too_many_arguments)] @@ -1057,6 +1092,7 @@ fn action_proof_n( index: usize, target: &Pubkey, beneficiary: &Pubkey, + relay: &Pubkey, action_accounts: u8, payload: &[u8], ) -> mirror_circuit::SolanaProof { @@ -1067,6 +1103,7 @@ fn action_proof_n( index, target, beneficiary, + relay, action_accounts, payload, INVOKE, @@ -1081,6 +1118,7 @@ fn action_proof_sel( index: usize, target: &Pubkey, beneficiary: &Pubkey, + relay: &Pubkey, action_accounts: u8, payload: &[u8], selector: u64, @@ -1091,6 +1129,7 @@ fn action_proof_sel( selector, &target.to_bytes(), &beneficiary.to_bytes(), + &relay.to_bytes(), RELAY_FEE, action_accounts, payload, @@ -1129,7 +1168,16 @@ fn a_crowd_of_members_perform_a_real_protocol_action_together() { // Every member sends the identical payload, which is what makes the // crowd a crowd: the actions are indistinguishable by content. let payload = b"mirror-pool".as_slice(); - let proof = action_proof(&keys, &tree, ¬es, i, &memo, &beneficiary, payload); + let proof = action_proof( + &keys, + &tree, + ¬es, + i, + &memo, + &beneficiary, + &relay.pubkey(), + payload, + ); let nullifier = proof.public_inputs[1]; let ix = action_ix( &env, @@ -1199,7 +1247,17 @@ fn an_action_runs_with_a_non_empty_account_list() { let payload = b"signed by the pool".as_slice(); // One action account, declared in the proof and therefore bound. - let proof = action_proof_n(&keys, &tree, ¬es, 0, &memo, &beneficiary, 1, payload); + let proof = action_proof_n( + &keys, + &tree, + ¬es, + 0, + &memo, + &beneficiary, + &relay.pubkey(), + 1, + payload, + ); let nullifier = proof.public_inputs[1]; let ix = action_ix_n( &env, @@ -1255,7 +1313,17 @@ fn a_settler_cannot_change_how_many_accounts_an_action_gets() { env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); let payload = b"one account".as_slice(); - let proof = action_proof_n(&keys, &tree, ¬es, 1, &memo, &beneficiary, 1, payload); + let proof = action_proof_n( + &keys, + &tree, + ¬es, + 1, + &memo, + &beneficiary, + &relay.pubkey(), + 1, + payload, + ); let nullifier = proof.public_inputs[1]; let ix = action_ix_n( &env, @@ -1314,7 +1382,17 @@ fn a_settler_cannot_place_the_vault_in_an_action_account_list() { env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); let payload = b"vault please".as_slice(); - let proof = action_proof_n(&keys, &tree, ¬es, 2, &memo, &beneficiary, 1, payload); + let proof = action_proof_n( + &keys, + &tree, + ¬es, + 2, + &memo, + &beneficiary, + &relay.pubkey(), + 1, + payload, + ); let nullifier = proof.public_inputs[1]; let ix = action_ix_n( &env, @@ -1386,6 +1464,7 @@ fn the_pool_signs_an_action_as_its_own_authority() { 0, &memo, &beneficiary, + &relay.pubkey(), 1, payload, INVOKE_SIGNED, @@ -1494,6 +1573,7 @@ fn a_signed_action_settles_inside_a_batch_of_plain_transfers() { 3, &memo, &signer_beneficiary, + &relay.pubkey(), 1, payload, INVOKE_SIGNED, @@ -1587,6 +1667,7 @@ fn the_pools_signature_cannot_be_turned_against_its_own_vault() { 1, &system, &beneficiary, + &relay.pubkey(), 2, &payload, INVOKE_SIGNED, @@ -1650,7 +1731,16 @@ fn a_relay_cannot_swap_the_target_program() { let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); let payload = b"hello".as_slice(); - let proof = action_proof(&keys, &tree, ¬es, 0, &memo, &beneficiary, payload); + let proof = action_proof( + &keys, + &tree, + ¬es, + 0, + &memo, + &beneficiary, + &relay.pubkey(), + payload, + ); let nullifier = proof.public_inputs[1]; // Proved for the memo program; submitted for something else. @@ -1680,7 +1770,16 @@ fn a_relay_cannot_alter_the_action_payload() { let beneficiary = Pubkey::new_unique(); let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); - let proof = action_proof(&keys, &tree, ¬es, 1, &memo, &beneficiary, b"stake 0.1"); + let proof = action_proof( + &keys, + &tree, + ¬es, + 1, + &memo, + &beneficiary, + &relay.pubkey(), + b"stake 0.1", + ); let nullifier = proof.public_inputs[1]; let ix = action_ix( @@ -1711,7 +1810,16 @@ fn an_action_cannot_re_enter_the_pool() { let self_target = env.program_id; let payload = b"reenter".as_slice(); - let proof = action_proof(&keys, &tree, ¬es, 2, &self_target, &beneficiary, payload); + let proof = action_proof( + &keys, + &tree, + ¬es, + 2, + &self_target, + &beneficiary, + &relay.pubkey(), + payload, + ); let nullifier = proof.public_inputs[1]; let ix = action_ix( &env, @@ -1758,7 +1866,7 @@ fn a_relay_cannot_declare_an_account_count_the_member_did_not_authorise() { env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); // Proved for a plain transfer, which takes no action accounts. - let proof = proof_for(&keys, &tree, ¬es, 0, &beneficiary); + let proof = proof_for(&keys, &tree, ¬es, 0, &beneficiary, &relay.pubkey()); let nullifier = proof.public_inputs[1]; let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); @@ -1813,8 +1921,15 @@ fn an_unknown_selector_is_refused_before_the_nullifier_burns() { env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); let merkle_proof = tree.proof(1).unwrap(); - let binding = - mirror_core::action_binding(99, &[0u8; 32], &beneficiary.to_bytes(), RELAY_FEE, 0, &[]); + let binding = mirror_core::action_binding( + 99, + &[0u8; 32], + &beneficiary.to_bytes(), + &relay.pubkey().to_bytes(), + RELAY_FEE, + 0, + &[], + ); let witness = Witness { note: notes[1], merkle_proof: &merkle_proof, @@ -1884,7 +1999,7 @@ fn squatting_a_spend_pda_does_not_brick_the_note() { let relay = Keypair::new(); env.svm.airdrop(&relay.pubkey(), 10_000_000_000).unwrap(); - let proof = proof_for(&keys, &tree, ¬es, 0, &beneficiary); + let proof = proof_for(&keys, &tree, ¬es, 0, &beneficiary, &relay.pubkey()); let nullifier = proof.public_inputs[1]; let (spend_pda, _) = spend_address(&env.program_id, &env.pool, &nullifier); From 66479e2895317d49a9b04fe816d248f48f0f6df6 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Tue, 28 Jul 2026 05:30:21 -0300 Subject: [PATCH 77/83] docs: the incentives, which were enforced but never written down MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The brief asks for a coordination layer, a privacy set, and the incentives that keep people in it. The first two were argued at length and the third appeared nowhere outside PLAN.md, where the only thing said about it was that the designed version had been cut. Read straight through, the repository looked like it had two of three. It has four, and they were all already in the program: - k_floor refuses a spend until a crowd exists, so arriving early is what lets other people act rather than something the pool punishes - the one-hour timeout is what makes that floor an incentive instead of coercion; the two rules only work as a pair, and removing either turns the other into a reason to leave - the relay fee pays somebody else to sign, which is what makes the anonymity-preserving path economically available and not merely permitted - FeeNotUniform refuses a mixed-fee batch outright, so converging on a common fee is a rule rather than advice in a usage guide INCENTIVES.md states each with the line that enforces it, then states the gap plainly: nothing pays a member for dwell. It also says why the obvious fix is the wrong one. A reward has to be paid to somebody, naming an address on chain is the one thing this design spends everything else avoiding, and a per-identity reward counter and an anonymous path are mutually exclusive by construction — so the mechanism would pay people to destroy the behaviour it exists to reward. The version that would work is specified rather than gestured at: a second nullifier per note, claimed against the same membership statement, gated on the same floor, paid to a fresh address, with dwell read from data already on chain so no per-identity counter is stored. That is a new instruction, a new nullifier domain, a changed note format and a changed circuit, and this repository's standard for a value-bearing path is a real SVM with negative cases and a live cluster with published signatures. Absent rather than half-present, and scoped rather than vague. Also adds the README section that answers the three parts in order, since a reader looking for the brief's own words should not have to assemble the answer from four documents. --- README.md | 43 +++++++++++++ docs/INCENTIVES.md | 150 +++++++++++++++++++++++++++++++++++++++++++++ docs/PLAN.md | 6 ++ 3 files changed, 199 insertions(+) create mode 100644 docs/INCENTIVES.md diff --git a/README.md b/README.md index 92101a35..3edb6323 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,48 @@ So this project claims exactly two things: Anything we cannot support with a measurement whose method is published, we do not say. There is a section below of things we deliberately do not claim. +## The three parts, and where each one stands + +A mirror-pool is asked for three things: a **coordination layer**, a **privacy +set**, and the **incentives that keep people in it**. Taking them one at a time, +including the one where the answer is partly no. + +**The coordination layer is the two-phase epoch.** `submit_spend` verifies a +proof and burns a nullifier, paying out nothing; `settle_epoch` executes the +whole batch in one transaction, so every member's action lands at one timestamp, +in one ordering, under one signature. Arrival time cannot tell the members apart, +because there is only one arrival. A settlement of **20 actions with a single +required signature** is on devnet, at the 64-account lock ceiling, and every +account in it is named through a lookup table the settler publishes and then +closes. + +**The privacy set is the note tree, and its honest size is measured rather than +asserted.** A member proves in zero knowledge that they own *some* note, never +which. What that hides on the action side is closed — actions execute from the +pool's vault, so their funding trace leads to the pool and is identical for +everyone. What it does not close is where each member's *deposit* came from, and +that channel is measured against live mainnet data with the method published +beside the number. Nominal `k` is the tree; effective `k` is smaller; this +repository publishes both and never quotes the first alone. + +**The incentives are structural, and one of them is missing.** Four are enforced +by the program rather than recommended: you cannot act until a crowd exists +(`k_floor`), waiting is never a hostage situation (the one-hour timeout), a relay +is paid out of the denomination to sign so that you never do, and a batch whose +members paid different fees is refused outright — so converging on a common fee +is a rule, not advice. + +What is **not** here is a reward for dwell — for holding a note longer than you +needed to. It was designed, and it was cut with the entry fee that would have +funded it, because a fee with no payout path is a fund trap rather than a +half-built feature. The deeper reason it stayed cut is that a reward must be paid +to somebody, and naming a member's address on chain costs that member all of +their anonymity at the exact moment they are being rewarded for protecting +everyone else's. The anonymity-preserving version — a second nullifier, claimed +under the same floor, paid to a fresh address — is specified in +[`docs/INCENTIVES.md`](docs/INCENTIVES.md) rather than sketched, and is absent +rather than half-present. + ## What is here | | | @@ -753,6 +795,7 @@ reporting. | `docs/GROTH16_INTEGRATION.md` | The arkworks-to-Solana byte layout, verified by execution. | | `docs/MEASUREMENT_LOG.md` | Every run. | | `docs/THREAT_MODEL.md` | The adversary, what holds, and every place it stops. | +| `docs/INCENTIVES.md` | What keeps a member in the pool, enforced by the program — and the one reward that is deliberately absent. | | `docs/PROOF.md` | Devnet signatures for every flow, and the rejections. | | `docs/CROWD.md` | Six members delegating to six different validators in one devnet transaction, and what divergence costs. | | `docs/USAGE.md` | The member-facing commands, end to end, with real devnet output. | diff --git a/docs/INCENTIVES.md b/docs/INCENTIVES.md new file mode 100644 index 00000000..5a4cdcc8 --- /dev/null +++ b/docs/INCENTIVES.md @@ -0,0 +1,150 @@ +# Incentives + +A pool that nobody stays in has an anonymity set of one, so the question of what +keeps members in it is a design question and not a marketing one. This document +answers it in three parts: what the protocol actually does to align a member's +interest with everyone else's, what it deliberately does *not* pay for, and what +the missing piece would have to look like to be worth shipping. + +The short version: **the incentives here are structural, not monetary.** Nothing +in this protocol pays you. Several things in it make the cooperative move the +only one available, or the cheapest one, and those are enforced by the program +rather than recommended by a README. + +## The four that are enforced + +Each of these is a rule in the on-chain program, not a convention. The citation +is where to check it. + +### You cannot act until a crowd exists + +`processor.rs:342` refuses `submit_spend` when the pool holds fewer notes than +its `k_floor`: + +```rust +if deposit_count < k_floor as u64 { + return Err(MirrorProgramError::BelowAnonymityFloor.into()); +} +``` + +This is the load-bearing one. In a pool with no floor, the first member to act +acts alone, gets nothing from the pool, and learns that arriving early is +punished — so nobody arrives early and the pool never starts. The floor inverts +that: your deposit is what lets *other* people act, and theirs is what lets you. +Waiting is not patience, it is the protocol. + +What it bounds is program-visible membership — how many notes the tree holds. It +is not the effective anonymity set, which is smaller, and `THREAT_MODEL.md` says +so at length rather than letting the floor stand in for a guarantee. + +### Waiting is never a hostage situation + +A floor with no escape is coercion, not an incentive: it would mean a quiet pool +holds your escrow indefinitely and no authority can release it. So +`SETTLE_TIMEOUT_SECONDS` (`processor.rs:510`) is one hour, after which a batch +settles whether or not it reached the floor. + +The two rules only work together. The floor is what makes waiting valuable; the +timeout is what makes waiting safe. Removing either one turns the other into a +reason to leave. + +The timeout has a cost and `THREAT_MODEL.md` argues it rather than hiding it: a +batch that settles on the clock has no floor, so a batch of one is reachable by +anyone willing to wait an hour. `mirror settle` warns when it is about to publish +one. + +### Somebody else is paid to sign for you + +A member who signs their own spend publishes the link the pool exists to break. +So spends are relay-signed, and the relay is paid out of the denomination — the +member receives `denomination - relay_fee`. + +That fee is the only value transfer in the protocol, and it exists to make the +anonymity-preserving path economically available rather than merely permitted. +Relaying is permissionless: no allowlist, no registration, no authority whose +absence freezes the pool. + +This is also why the relay is inside the action binding. A fee that can be +sniped by whoever lands the transaction first is a fee no relay can count on, and +a pool with no relays is a pool where members submit from their own wallets and +deanonymise themselves. `THREAT_MODEL.md` has that attack in full. **Protecting +the incentive layer is the same work as protecting the privacy layer** — that is +not an analogy, it is the same code. + +### Paying what everybody else pays is enforced, not advised + +A member is paid `denomination - relay_fee`, and that figure lands in a public +account balance. A batch mixing fees therefore pays visibly different amounts and +an observer partitions it by value without breaking a proof. + +`processor.rs:634` refuses such a batch outright with `FeeNotUniform`, and +`mirror settle` groups pending spends by fee and settles the largest group rather +than assembling a transaction the program will reject. + +So the incentive to converge on a common fee is not a suggestion in a usage +guide. Deviating does not get you a worse anonymity set; it gets your batch +refused until you settle with members who paid what you did. + +## The one that is not there + +**Nothing pays a member for dwell** — for leaving a note unspent longer than they +needed to, which is the behaviour that makes everyone else's set larger. + +`PLAN.md` designed exactly that: entry fees accumulating in a reward pool, paid +pro-rata to dwell, capped per epoch and gated on the floor. None of it shipped, +and `Pool::initialise` now refuses a nonzero entry fee outright +(`state.rs:208`), so the fee that would have funded it cannot even be collected. + +The reason the fee went too is worth stating, because it is the smaller mistake +that would have looked like the safer one. A fee with no payout path is not a +half-built feature, it is a fund trap: lamports every depositor pays, that no +instruction can return, and that no authority exists to release — because this +program deliberately has no such authority. Shipping the fee and documenting the +gap would have left the trap armed behind a paragraph. + +## Why the obvious fix is the wrong one + +A reward has to be paid to somebody. Paying somebody on Solana means naming an +address in a transaction. And the entire claim of this protocol is that **no +member key appears on chain at any point after the deposit** — spends are +relay-signed, settlements are settler-signed, and actions execute from the pool's +vault. + +So a dwell reward paid to a member's address does not cost a little anonymity. It +costs all of it, for that member, permanently, and it does so at exactly the +moment they are being rewarded for having protected everyone else's. The +mechanism would pay people to destroy the thing it exists to reward. + +This is not hypothetical, and it is the shape the problem takes wherever it is +attempted: a reward path that pays an identity only functions on a path that +*has* identities — which is the path without anonymity. An anonymous path and a +per-identity reward counter are mutually exclusive by construction, not by +oversight. + +## What would actually work, specified + +The anonymity-preserving version is a second nullifier, and it is a real design +rather than a hand-wave: + +- A note commits to a second secret alongside its spend secret, so a member holds + two independent nullifiers for one note. +- `claim_reward` takes a Groth16 proof of the same membership statement the spend + path uses, over the *reward* nullifier, and pays a fresh address the prover + names in the action binding. +- The claim is gated on the same `k_floor`, for the same reason: a reward + claimable by a crowd of one is a refund with extra steps, and it would let a + lone participant recycle their own fee back to themselves. +- Dwell is measured from the note's leaf index and the claim's slot, both already + on chain, so no per-identity counter is stored and none can be read. + +That is a second value-bearing instruction, a second nullifier domain, a changed +note format, and a changed circuit — and every one of those is a place where the +audit trail in this repository found somebody else's bug. This repository's +standard for a value-bearing path is that it is exercised against a real SVM with +negative cases carrying the program's own error codes, and against a live cluster +with the signatures published. That standard is the reason to defer this rather +than the obstacle to it. + +**So it is scoped, not vague, and absent rather than half-present.** The pool +ships with the incentives it can enforce, and without the one it cannot yet pay +safely. diff --git a/docs/PLAN.md b/docs/PLAN.md index d61bd70b..b50ef526 100644 --- a/docs/PLAN.md +++ b/docs/PLAN.md @@ -161,6 +161,12 @@ without a crowd gate is not an incentive, it is a refund with extra steps. > **Shipped instead:** none of this. The dwell mechanism was cut, and once it > was, the fee funding it had no recipient — so the fee was cut as well, by > refusing any nonzero value at pool creation. See *What actually got cut*. +> +> What did ship is four incentives the program enforces rather than pays for, and +> the reason the monetary one is harder than it looks: a reward has to name an +> address, and naming a member's address is the one thing this design spends +> everything else avoiding. `INCENTIVES.md` has all four, the gap, and the +> second-nullifier design that would close it. ### Provenance measurement From 0279fa51c83adee77d40fe4f2c38faec784a0af2 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Tue, 28 Jul 2026 05:53:05 -0300 Subject: [PATCH 78/83] feat: the delegation ceiling through a lookup table, which CROWD.md had left open MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The last version of that document ended by naming what it had not measured: "the ceiling through a table is unmeasured — not that it is the same number." This answers it with the same method the packet ceilings use, by building the real instruction and counting what it names. batch legacy through a table everyone to one validator 7 18 a validator each 6 13 Both roughly double, and the interesting part is that the gap between them widens from one member to five. Bytes are spent naming an account; locks are held per distinct account. A shared vote account is named once either way and locked once too, so agreeing on a validator is worth more under locks than it was under bytes — the divergence trade this whole run exists to price gets steeper in the regime where the batches are bigger. The compute-budget instruction is inside both figures because at this size it stops being optional: thirteen delegations run to roughly 309k CU at the per-member rate the live run measured, well past the 200,000 a transaction gets by default. Asking for more brings the compute-budget program, and a program is an account. So raising the budget still costs a member — forty bytes in a legacy packet, one lock through a table. The escape from one limit is paid out of the other in both regimes, which is the result rather than the annoyance. What these two are not: a settled batch of that size. The document says so in those words. The 64-account limit they are measured against is not a guess — it came off devnet, where 77 accounts returned TooManyAccountLocks and the twenty-transfer settlement landed at exactly 64 — but a thirteen-member delegation through a table has not been sent and is not claimed. render_only recomputes these three fields instead of reading them. A record written before they existed deserializes them as zero, and a zero rendered into a published table would read as a measured result, which is worse than any staleness the recompute could introduce. It touches no cluster: same instruction, same count. --- README.md | 27 +++++ crates/mirror-cli/src/crowd.rs | 192 +++++++++++++++++++++++++++++++-- docs/CROWD.md | 13 ++- 3 files changed, 222 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3edb6323..d7e34106 100644 --- a/README.md +++ b/README.md @@ -319,6 +319,33 @@ settler is never handed a transaction the cluster will refuse, and `init-pool` refuses a crowd floor higher than one settlement can carry rather than letting a pool be created that can only ever settle on its timeout. +**A delegation batch moves too, and by more.** Counting locks instead of bytes +for the two stake shapes gives: + +| the batch | legacy packet | through a table | +|---|---|---| +| stake delegations, everyone to the same validator | 7 | **18** | +| stake delegations, a different validator each | 6 | **13** | + +Both roughly double, and the gap between them widens from one member to five — +because a shared vote account is named once either way but *locked* once too, so +agreeing on a validator buys more under locks than it did under bytes. + +Those two figures include the `SetComputeUnitLimit` instruction, because at this +size it is not optional: thirteen delegations cost on the order of 309,000 CU at +the per-member rate `docs/CROWD.md` measured, well past the 200,000 a transaction +is given by default. Asking for more brings the compute-budget program along, and +a program is an account — so raising the budget still costs a member, forty bytes +in a legacy packet and one lock through a table. **The escape from one limit is +paid for out of the other in both regimes**, which is the result rather than the +inconvenience. + +These two are computed the way the packet ceilings are — by building the real +instruction and counting what it names — and the 64-account limit they are +measured against was itself taken from devnet. A thirteen-member delegation batch +settled through a table on a live cluster is *not* something this repository +claims, and `docs/CROWD.md` says so in the same words. + **The table is not free, and it is taken back down.** It costs four extra transactions, a slot of latency, and rent — and, more to the point, while it exists it is a public durable account listing every address the settlement is diff --git a/crates/mirror-cli/src/crowd.rs b/crates/mirror-cli/src/crowd.rs index db623ee2..91459574 100644 --- a/crates/mirror-cli/src/crowd.rs +++ b/crates/mirror-cli/src/crowd.rs @@ -26,6 +26,7 @@ //! cluster. use crate::chain::Chain; +use crate::lookup::MAX_ACCOUNT_LOCKS as MAX_LOCKS; use crate::note::StoredNote; use crate::soak::{keys, read_keypair, Settlement, Step}; use crate::stake; @@ -100,6 +101,29 @@ pub struct Ceiling { /// compute budget is the obvious answer to a settlement that runs out of it, /// and it is paid for in the one currency a full batch has none of. pub budget_bytes: usize, + + /// The same two ceilings when the packet is taken out of the way by a lookup + /// table, so what binds is the 64-account lock limit instead of 1232 bytes. + /// + /// A table names an account with one byte instead of thirty-two, which ends + /// the byte argument and starts a different one. These are the answer to the + /// question the earlier version of this report left open rather than + /// guessed at. + /// + /// Both figures assume the `SetComputeUnitLimit` instruction is present, + /// because for this shape it has to be: a batch large enough to be worth a + /// table costs more compute than the 200,000 a transaction is given by + /// default. That instruction brings its own program, and a program is an + /// account — so raising the budget still costs a member, just in locks now + /// rather than in bytes. + #[serde(default)] + pub divergent_through_table: usize, + #[serde(default)] + pub uniform_through_table: usize, + /// Locks held by a full divergent settlement through a table, against the 64 + /// a transaction may hold. + #[serde(default)] + pub locks_at_divergent_table: usize, } /// Everything the report is rendered from. @@ -646,18 +670,56 @@ impl Crowd { .rfind(|n| self.settlement_bytes(&shape(*n, share_vote)) <= PACKET_DATA_SIZE) .unwrap_or(0) }; + let largest_through_table = |share_vote: bool| -> usize { + (1..=64) + .rfind(|n| self.settlement_locks_with_budget(&shape(*n, share_vote)) <= MAX_LOCKS) + .unwrap_or(0) + }; let divergent = largest(false); let uniform = largest(true); let full = shape(divergent, false); let divergent_bytes = self.settlement_bytes(&full); + let divergent_through_table = largest_through_table(false); Ceiling { divergent, uniform, divergent_bytes, over_bytes: self.settlement_bytes(&shape(divergent + 1, false)), budget_bytes: self.settlement_bytes_with_budget(&full) - divergent_bytes, + divergent_through_table, + uniform_through_table: largest_through_table(true), + locks_at_divergent_table: self + .settlement_locks_with_budget(&shape(divergent_through_table, false)), } } + + /// How many accounts a settlement would lock, with the compute-budget + /// instruction counted. + /// + /// This is the limit that takes over once a lookup table ends the byte + /// argument, and it is a different kind of limit: bytes are spent per + /// account *name*, locks are held per *distinct* account. That is the whole + /// reason a crowd agreeing on one validator fits more members — the shared + /// vote account is named once and locked once, where divergent members each + /// bring one nobody else in the batch holds. + /// + /// The compute-budget program is included because it is an account like any + /// other. A batch this size cannot execute inside the default 200,000 CU, so + /// the instruction is not optional here, and neither is the lock it costs. + pub fn settlement_locks_with_budget(&self, batch: &[Settlement]) -> usize { + let ix = self.settle_ix(batch); + let mut metas = ix.accounts.clone(); + metas.push(AccountMeta::new_readonly(compute_budget_program(), false)); + crate::lookup::locks_for(&metas, &ix.program_id, &self.payer.pubkey()) + } +} + +/// The compute-budget program, which a settlement large enough to need a lookup +/// table also needs. +fn compute_budget_program() -> Pubkey { + "ComputeBudget111111111111111111111111111111" + .parse() + .expect("the compute budget program id is a const") } pub fn run(program: &str, url: &str, keypair: &str, out: &std::path::Path) -> Result<()> { @@ -877,7 +939,30 @@ pub fn render_only(out: &std::path::Path) -> Result<()> { let text = std::fs::read_to_string(&result_path).with_context(|| { format!("{result_path} does not exist — there is no recorded run to render") })?; - let outcome: Outcome = serde_json::from_str(&text)?; + let mut outcome: Outcome = serde_json::from_str(&text)?; + + // The through-a-table ceilings are a property of the instruction's shape and + // not of the run, so they are recomputed here rather than trusted from the + // file. A record written before these fields existed deserializes them as + // zero, and rendering a zero into a published table is worse than any + // staleness this is meant to avoid: it would read as a measured result. + // + // Nothing about this touches a cluster. It builds the same instruction the + // settlement builds and counts what it names, which is what the packet + // ceilings in the same struct already are. + let derived = Crowd::new( + "http://127.0.0.1:1", + outcome + .pool + .parse() + .unwrap_or_else(|_| solana_program::pubkey::Pubkey::new_unique()), + Keypair::new(), + ) + .measure_ceiling(); + outcome.ceiling.divergent_through_table = derived.divergent_through_table; + outcome.ceiling.uniform_through_table = derived.uniform_through_table; + outcome.ceiling.locks_at_divergent_table = derived.locks_at_divergent_table; + write_report(&outcome, out)?; println!("rendered {} from {result_path}", out.display()); Ok(()) @@ -1036,18 +1121,48 @@ fn report(outcome: &Outcome) -> String { instruction. Measured against this very batch, that instruction costs **{} \ bytes**, and a full legacy settlement has {} to spare. In a legacy transaction, \ raising the budget means dropping a member.\n\n\ - **A lookup table lifts that, and this run did not measure how far.** Naming \ - accounts by one byte each takes the packet out of the way — `mirror settle` does \ - it automatically, and a batch of twenty plain transfers settled that way on devnet \ - at 332 bytes of 1232. What then binds a batch of *delegations* is some combination \ - of the 64-account lock limit and the compute this table already shows to be \ - expensive, and neither has been measured for this shape. The honest statement is \ - that {} is the legacy ceiling for divergent delegations and the ceiling through a \ - table is unmeasured — not that it is the same number.\n\n", + **A lookup table lifts that, and here is how far.** Naming accounts by one \ + byte each takes the packet out of the way — `mirror settle` does it \ + automatically, and a batch of twenty plain transfers settled that way on devnet \ + at 332 bytes of 1232. What takes over for *delegations* is the 64-account lock \ + limit, and it is a different kind of limit: bytes are spent naming an account, \ + locks are held per **distinct** account.\n\n\ + | batch | legacy packet | through a lookup table |\n|---|---|---|\n\ + | all delegating to the same validator | {} | **{}** |\n\ + | each delegating to a different one | {} | **{}** |\n\n\ + A full divergent batch through a table holds {} of the 64 locks a transaction \ + may take. Both ceilings roughly double, and the gap between them widens from one \ + member to {} — because a shared vote account is named once either way but locked \ + only once too, so agreeing on a validator is worth more here than it was in the \ + packet.\n\n\ + **The compute budget instruction is counted in those two figures, because at \ + this size it is not optional.** A batch of {} delegations costs on the order of \ + {} CU at the per-member rate this run measured, well past the 200,000 a \ + transaction is given by default. Asking for more brings the compute-budget \ + program along, and a program is an account — so raising the budget still costs a \ + member. In the legacy packet that cost was {} bytes; through a table it is one \ + lock. The escape from one limit is paid out of the other in both regimes, which \ + is the finding rather than the inconvenience.\n\n\ + **What kind of number these two are.** They are computed the same way the packet \ + ceilings above are — by building the real instruction and counting what it \ + names — and not by settling a batch of that size. The 64-account limit itself is \ + not a guess: it was found on devnet, where 77 accounts returned \ + `TooManyAccountLocks`, and the twenty-transfer settlement cited above landed at \ + exactly 64. What has not been done is a delegation batch of {} settled through a \ + table on a live cluster, and this document does not claim one.\n\n", ceiling.divergent, ceiling.budget_bytes, PACKET_DATA_SIZE - ceiling.divergent_bytes, + ceiling.uniform, + ceiling.uniform_through_table, ceiling.divergent, + ceiling.divergent_through_table, + ceiling.locks_at_divergent_table, + ceiling.uniform_through_table - ceiling.divergent_through_table, + ceiling.divergent_through_table, + ceiling.divergent_through_table * 23_800, + ceiling.budget_bytes, + ceiling.divergent_through_table, )); md.push_str("## Every step\n\n"); @@ -1131,3 +1246,62 @@ fn report(outcome: &Outcome) -> String { ); md } + +#[cfg(test)] +mod tests { + use super::*; + + /// A `Crowd` with no cluster behind it. + /// + /// `measure_ceiling` never touches the network — it serializes instructions + /// and counts accounts — so the ceilings are testable without devnet, which + /// is what makes them a pinned result rather than something re-derived on + /// each run and believed. + fn offline() -> Crowd { + Crowd::new("http://127.0.0.1:1", Pubkey::new_unique(), Keypair::new()) + } + + /// The four ceilings, pinned. + /// + /// Legacy and through-a-table are different regimes and the numbers say so. + /// A change to the account shape of `settle_epoch` moves these, and moving + /// them silently is how a document comes to describe a settlement nobody can + /// send. + #[test] + fn the_table_lifts_the_delegation_ceiling_and_locks_take_over() { + let c = offline().measure_ceiling(); + + // Legacy: the 1232-byte packet binds, and divergence costs a member. + assert_eq!(c.uniform, 7, "legacy, one validator"); + assert_eq!(c.divergent, 6, "legacy, a validator each"); + + // Through a table: the packet stops mattering and the 64-account lock + // limit takes over. Both shapes roughly double. + assert_eq!(c.uniform_through_table, 18, "table, one validator"); + assert_eq!(c.divergent_through_table, 13, "table, a validator each"); + + assert!( + c.locks_at_divergent_table <= MAX_LOCKS, + "a full divergent batch holds {} locks, over the {MAX_LOCKS} limit", + c.locks_at_divergent_table + ); + } + + /// Divergence costs more under locks than under bytes, and the reason is + /// structural rather than incidental. + /// + /// Bytes are spent naming an account; locks are held per *distinct* + /// account. A shared vote account is named once either way, so agreeing on a + /// validator buys one member in a legacy packet and five through a table. + #[test] + fn agreeing_on_a_validator_buys_more_members_through_a_table_than_without() { + let c = offline().measure_ceiling(); + let legacy_gain = c.uniform - c.divergent; + let table_gain = c.uniform_through_table - c.divergent_through_table; + assert!( + table_gain > legacy_gain, + "divergence cost {legacy_gain} member(s) legacy and {table_gain} through a table; \ + the table was supposed to make the shared account matter more, not less" + ); + } +} diff --git a/docs/CROWD.md b/docs/CROWD.md index 366a0b5c..691b5a06 100644 --- a/docs/CROWD.md +++ b/docs/CROWD.md @@ -51,7 +51,18 @@ Each member's Groth16 proof was verified earlier, in their own `submit_spend`, w The packet binds first — 6 members is where the bytes run out, and the budget is not exhausted there — but for a **legacy** transaction the two limits are barely independent, and that is worth stating plainly. The usual answer to a settlement that runs out of compute is to ask for more with a `SetComputeUnitLimit` instruction. Measured against this very batch, that instruction costs **40 bytes**, and a full legacy settlement has 38 to spare. In a legacy transaction, raising the budget means dropping a member. -**A lookup table lifts that, and this run did not measure how far.** Naming accounts by one byte each takes the packet out of the way — `mirror settle` does it automatically, and a batch of twenty plain transfers settled that way on devnet at 332 bytes of 1232. What then binds a batch of *delegations* is some combination of the 64-account lock limit and the compute this table already shows to be expensive, and neither has been measured for this shape. The honest statement is that 6 is the legacy ceiling for divergent delegations and the ceiling through a table is unmeasured — not that it is the same number. +**A lookup table lifts that, and here is how far.** Naming accounts by one byte each takes the packet out of the way — `mirror settle` does it automatically, and a batch of twenty plain transfers settled that way on devnet at 332 bytes of 1232. What takes over for *delegations* is the 64-account lock limit, and it is a different kind of limit: bytes are spent naming an account, locks are held per **distinct** account. + +| batch | legacy packet | through a lookup table | +|---|---|---| +| all delegating to the same validator | 7 | **18** | +| each delegating to a different one | 6 | **13** | + +A full divergent batch through a table holds 61 of the 64 locks a transaction may take. Both ceilings roughly double, and the gap between them widens from one member to 5 — because a shared vote account is named once either way but locked only once too, so agreeing on a validator is worth more here than it was in the packet. + +**The compute budget instruction is counted in those two figures, because at this size it is not optional.** A batch of 13 delegations costs on the order of 309400 CU at the per-member rate this run measured, well past the 200,000 a transaction is given by default. Asking for more brings the compute-budget program along, and a program is an account — so raising the budget still costs a member. In the legacy packet that cost was 40 bytes; through a table it is one lock. The escape from one limit is paid out of the other in both regimes, which is the finding rather than the inconvenience. + +**What kind of number these two are.** They are computed the same way the packet ceilings above are — by building the real instruction and counting what it names — and not by settling a batch of that size. The 64-account limit itself is not a guess: it was found on devnet, where 77 accounts returned `TooManyAccountLocks`, and the twenty-transfer settlement cited above landed at exactly 64. What has not been done is a delegation batch of 13 settled through a table on a live cluster, and this document does not claim one. ## Every step From e4a3c7872f49a5c7b9ac3413b8f1b6ffe026b012 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Tue, 28 Jul 2026 06:07:49 -0300 Subject: [PATCH 79/83] docs: a README that answers the brief before it explains itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The evidence was all here and a reader had to assemble it. Someone arriving with the brief in hand had no row to check "Rust, end to end" against, no single place saying what is different, and no architecture short of the long version. Three sections now sit above everything that was already written, and nothing below them was removed. **Meeting the brief** is a table, one row per thing asked, with a third column saying where to check it rather than asking to be believed. The Rust-only row carries the command that proves it and returns nothing. **What is different here** is six claims, each phrased as a fact about this repository. Every number is taken rather than modelled, and the one figure that is derived rather than landed says so where it is published. The setup is reproducible and a test compares the whole verifying key rather than one element of it. The pairing runs on chain in the program's own syscall. A note pays out once ever, and the denomination is a pool constant rather than a note field, so the class of bug where escrow and payout disagree cannot be written down. Every command the documents mention exists. And the metric is turned on our own run, where it returns its best possible value and the section explains why that is worthless. **The architecture, in one page** gives the two-phase shape, why it is two phases — n proofs inside one settlement would blow the budget at six members — what the three public inputs are, and why the pool signs. The long version stays where it was. Also adds the delegation ceilings through a lookup table to the crowd section, and moves the test count to 261. --- README.md | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d7e34106..b0ee4e67 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,59 @@ delegation or a governance vote needs and what a transfer cannot do. Rust end to end. MIT. No Anchor, no Circom, no JavaScript anywhere in the proving path. +## Meeting the brief + +Every row is checkable in this repository, and the right-hand column says where. + +| what is asked | how this meets it | check it | +|---|---|---| +| **Rust, end to end** | **Zero** files of any other language are tracked here. No Circom, no snarkjs, no `ethers`, no TypeScript build step, no shell scripts doing real work. The circuit is an arkworks R1CS gadget in `crates/mirror-circuit`; the prover is Rust; the verifier is the on-chain program calling the `alt_bn128` syscall. | `git ls-files '*.js' '*.ts' '*.py' '*.sol' '*.circom'` returns nothing | +| **Production-grade, tested, deployable** | 261 tests. The end-to-end suite loads the compiled `.so` into a real SVM and verifies real Groth16 proofs through the actual syscall. Negative cases assert the program's *own* error codes, not that something failed. `overflow-checks` on in release; `cargo-deny` over advisories, bans, licences and sources; CI actions pinned by commit SHA. | `make verify` | +| **Deployed and running** | Live on devnet, with every claim in this file linking to the transaction behind it. The full lifecycle — pool, deposits, proofs, batched settlement, and four rejections — is recorded with signatures. | [`docs/PROOF.md`](docs/PROOF.md) | +| **Scalable & customizable** | Adding a protocol requires no change to the on-chain program. Selector 1 invokes any program with any payload; selector 2 additionally makes the pool *sign* as the member's authority, which is what a stake delegation or a governance vote needs. A real `DelegateStake` runs through it on devnet. | [`docs/USAGE.md`](docs/USAGE.md) | +| **Realistic** | The anonymity number is computed from live mainnet chain data, with the sample committed so the result reproduces without RPC access — and it is pointed at a pool this project neither controls nor funded, because measuring our own empty pool would be measuring nothing. | [`docs/MEASUREMENT_LOG.md`](docs/MEASUREMENT_LOG.md) | +| **Well-documented** | Ten documents: install path, architecture, threat model, proof of life, measurement method, and the design as decided with every departure from it recorded. | [below](#documentation) | +| **Open source, MIT** | MIT at the workspace root and on every crate. | [`LICENSE`](LICENSE) | + +## What is different here + +Six things, stated as facts about this repository rather than as comparisons. + +**Every number is taken, not modelled.** There is no simulated distribution +anywhere in the measurement path. The provenance figures come from real mainnet +funding chains; the packet and lock ceilings come from serializing the real +instruction; the compute figures come from a real SVM and a real cluster. Where a +number is derived rather than landed — the delegation ceiling through a lookup +table is the one case — the document that publishes it says so in those words. + +**The trusted setup is reproducible, and a test enforces it.** The seed is +committed in plain sight, the proving key is derived from it rather than +gitignored, and `vk_drift.rs` compares the *whole* verifying key — `alpha_g1`, +`beta_g2`, `gamma_g2`, `delta_g2` and every element of `gamma_abc_g1` — against +what that seed produces, plus the digest this file publishes. A check that bound +only `delta` would accept a key belonging to a different circuit. + +**The proof is verified by the chain, not by a stand-in.** `submit_spend` runs +the pairing on-chain in about 101,000 CU — half the default budget for one +instruction — through the program's own syscall. No committee, no multisig, and +no off-chain verifier standing in for one. + +**A note pays out once, ever.** The nullifier set is global rather than scoped to +an epoch, so no boundary can reopen a spend, and a test asserts the rejection +rather than assuming it. The denomination is a constant of the pool rather than a +field on the note, so the class of bug where the escrowed amount and the paid +amount disagree is not expressible. + +**Every command these documents mention exists.** `make verify` exercises the +tool the usage guide describes, and the walkthrough's output was produced by +running it against devnet rather than written by hand. + +**The measurement is turned on this project too.** `docs/CROWD.md` runs the same +metric against our own devnet crowd, where it returns the best value the metric +can produce — and then says why that number is worthless: every note in that pool +was funded by one wallet, so the partition has one class and the class is us. A +measurement apparatus that only ever points outward is one nobody has tested. + ## Getting it running Two prerequisites, and only two. @@ -41,7 +94,7 @@ Then: ``` git clone https://github.com/solanabr/mirror-pool && cd mirror-pool -make verify # fmt, clippy -D warnings, build-sbf, 259 tests +make verify # fmt, clippy -D warnings, build-sbf, 261 tests ``` Nothing in that command needs a network, an API key or an account with anybody, @@ -65,8 +118,51 @@ cargo build --release # target/release/mirror [`docs/USAGE.md`](docs/USAGE.md) walks the whole journey from there — creating a note through to settling a batch — with real devnet output for every command. -[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) is the design and why each -decision is what it is. + +## The architecture, in one page + +Four instructions, two phases, four crates behind one program. `init_pool` +creates a denomination's pool and is permissionless; the other three are the +member's path: + +``` + deposit submit_spend settle_epoch + ─────── ──────────── ──────────── + member escrows D relay signs, member anyone signs + commitment enters never does whole batch, one + the Merkle tree Groth16 verified transaction, one + no member key on on chain, nullifier timestamp, paid + chain after this burned, nothing paid from the vault PDA +``` + +**Why two phases and not one.** Verifying `n` proofs inside one settlement would +cost `n × 101,000` CU and blow the budget at six members. Splitting them means the +expensive step is per-member and parallel, and the step that must be atomic — +the one that gives every member the same timestamp and the same ordering — is +cheap. A batch of twenty payouts settles in 35,895 CU. + +**What the proof says.** Three public inputs: the Merkle root, the nullifier, and +an action binding. The binding is a keccak digest over the selector, the target +program, the beneficiary, the relay, the relay fee, the declared account count and +the payload — recomputed on-chain from the action about to execute, never +transmitted. A relay that alters any of them produces a different binding and the +pairing fails. + +**Why the pool signs.** Actions execute from the pool's vault PDA, so the +on-chain trace of an action is identical whoever asked for it. Under +`SELECTOR_INVOKE_SIGNED` the vault is a signer of the inner call, which is how a +member delegates stake without ever being the staker authority themselves. + +| crate | what it is | +|---|---| +| `programs/mirror-pool` | The on-chain program. Four instructions, no Anchor. | +| `crates/mirror-core` | Field, Poseidon, Merkle accumulator, notes. Linked on-chain, so host and program cannot drift. | +| `crates/mirror-circuit` | The R1CS gadget, the prover, and the verifying-key export. | +| `crates/mirror-provenance` | The funding-provenance measurement, and its honesty checks. | +| `crates/mirror-cli` | `mirror` — the member's tool and the operator's. | + +[`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) is the long version, with the +reason behind each decision and the alternative it was chosen over. ## The problem this takes as its subject @@ -175,7 +271,7 @@ rather than half-present. | `crates/mirror-provenance` | The funding-provenance measurement. | | `crates/mirror-cli` | The tool. `init-pool`, `note-new`, `deposit`, `tree`, `spend`, `settle`, `disclose`, `disclose-verify` for members; `setup`, `verify-setup`, `soak`, `crowd`, `close-table` for operators; `check-endpoint`, `seeds`, `collect`, `analyze`, `compare`, `selection` for the measurement. | -**259 tests.** The end-to-end suite loads the `.so` that `make build-sbf` +**261 tests.** The end-to-end suite loads the `.so` that `make build-sbf` produces into a real SVM, sends real transactions, and verifies a real Groth16 proof through the actual syscall — so a divergence between what the host believes and what the chain does cannot pass unnoticed. From 04043f557cb1abcb088b7dfd05edb701e633672c Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Tue, 28 Jul 2026 06:41:32 -0300 Subject: [PATCH 80/83] docs: the scale question, the setup check, and how to add a protocol MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Four gaps from the review, and one number of mine that was wrong. **What a settlement ceiling is not.** The repository leads with "twenty members in one transaction" and never said, anywhere, that this is not the anonymity set. It is not: the set is the tree, twenty levels deep, so a pool holds up to 1,048,576 notes and a proof costs the same at any occupancy. A settlement bounds how many members share one timestamp, which is a different and narrower thing — a pool with more members than one batch holds settles in several and pays for it in timestamps, not in set size. Without that paragraph the headline number reads as a cap on k, which is the opposite of what it measures. I first wrote that tree as depth 16. It is 20. The wrong number was worse than no number, since the whole point of the paragraph is that the set is large. **verify-setup, as a command a reader can run.** The reproducible setup was claimed in prose and buried as a subcommand in a table of eighteen. It is now the differentiator it should be: one line, the real output, the published digest, and the reason the check binds the whole verifying key rather than delta alone — a transcript that bound only delta would certify a key belonging to a different circuit. With the pointer to why reproducible is not secure, because that distinction is the reason this is on devnet. **Why the bracket is not decoration.** Twenty-nine of eighty-three members did not resolve to a class. Counting them as one class each puts the loss factor at one end of the range and folding them into observed classes puts it at the other; both are assumptions, so the interval is the honest report. A figure published without one has quietly picked an assumption, which makes it partly a measure of how much of the graph the tracer could afford to walk. That argument was in PROVENANCE_METHOD.md and belongs beside the number. **docs/INTEGRATING.md.** The brief asks for easy extension with new protocols and the answer was diluted across a usage section. Adding a protocol needs no program change, no redeploy and no new circuit, and this is the four-step procedure with a worked DelegateStake — payload, account count, why the pool must sign, and why its withdraw authority deliberately is not the pool. It also states what the proof does not promise, which is which accounts fill the declared slots, and points at the run that checks that limit by reading every stake account back off the cluster rather than asserting it. Portability is now stated positively — same source, real proofs, x86_64 in CI and arm64 locally, no feature flags between them — rather than as a denial that anything is architecture-specific. Not done, and each for a reason: reporting a worst case per bucket alongside the loss factor would contradict the argument this README already makes, that "worst case is 1" describes heavy-tailed provenance in general and not the pool being measured. Naming the disclosure checks in USAGE.md was already done — the walkthrough prints all ten by name. The rest was polish. --- README.md | 70 ++++++++++++++++++++---- docs/INTEGRATING.md | 127 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 11 deletions(-) create mode 100644 docs/INTEGRATING.md diff --git a/README.md b/README.md index b0ee4e67..720a6450 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ Every row is checkable in this repository, and the right-hand column says where. | **Rust, end to end** | **Zero** files of any other language are tracked here. No Circom, no snarkjs, no `ethers`, no TypeScript build step, no shell scripts doing real work. The circuit is an arkworks R1CS gadget in `crates/mirror-circuit`; the prover is Rust; the verifier is the on-chain program calling the `alt_bn128` syscall. | `git ls-files '*.js' '*.ts' '*.py' '*.sol' '*.circom'` returns nothing | | **Production-grade, tested, deployable** | 261 tests. The end-to-end suite loads the compiled `.so` into a real SVM and verifies real Groth16 proofs through the actual syscall. Negative cases assert the program's *own* error codes, not that something failed. `overflow-checks` on in release; `cargo-deny` over advisories, bans, licences and sources; CI actions pinned by commit SHA. | `make verify` | | **Deployed and running** | Live on devnet, with every claim in this file linking to the transaction behind it. The full lifecycle — pool, deposits, proofs, batched settlement, and four rejections — is recorded with signatures. | [`docs/PROOF.md`](docs/PROOF.md) | -| **Scalable & customizable** | Adding a protocol requires no change to the on-chain program. Selector 1 invokes any program with any payload; selector 2 additionally makes the pool *sign* as the member's authority, which is what a stake delegation or a governance vote needs. A real `DelegateStake` runs through it on devnet. | [`docs/USAGE.md`](docs/USAGE.md) | +| **Scalable & customizable** | Adding a protocol requires no change to the on-chain program — no redeploy, no new circuit, no governance. Selector 1 invokes any program with any payload; selector 2 additionally makes the pool *sign* as the member's authority, which is what a stake delegation or a governance vote needs. The whole procedure is four steps with a worked `DelegateStake` that runs on devnet. | [`docs/INTEGRATING.md`](docs/INTEGRATING.md) | | **Realistic** | The anonymity number is computed from live mainnet chain data, with the sample committed so the result reproduces without RPC access — and it is pointed at a pool this project neither controls nor funded, because measuring our own empty pool would be measuring nothing. | [`docs/MEASUREMENT_LOG.md`](docs/MEASUREMENT_LOG.md) | -| **Well-documented** | Ten documents: install path, architecture, threat model, proof of life, measurement method, and the design as decided with every departure from it recorded. | [below](#documentation) | +| **Well-documented** | Eleven documents: install path, architecture, threat model, proof of life, measurement method, and the design as decided with every departure from it recorded. | [below](#documentation) | | **Open source, MIT** | MIT at the workspace root and on every crate. | [`LICENSE`](LICENSE) | ## What is different here @@ -45,12 +45,30 @@ instruction; the compute figures come from a real SVM and a real cluster. Where number is derived rather than landed — the delegation ceiling through a lookup table is the one case — the document that publishes it says so in those words. -**The trusted setup is reproducible, and a test enforces it.** The seed is -committed in plain sight, the proving key is derived from it rather than -gitignored, and `vk_drift.rs` compares the *whole* verifying key — `alpha_g1`, -`beta_g2`, `gamma_g2`, `delta_g2` and every element of `gamma_abc_g1` — against -what that seed produces, plus the digest this file publishes. A check that bound -only `delta` would accept a key belonging to a different circuit. +**The trusted setup is reproducible, and you can check it in one command.** The +seed is committed in plain sight and the proving key is derived from it rather +than withheld, so anyone can regenerate the key the deployed program verifies +against: + +``` +$ mirror verify-setup --expect b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7 + +vk sha256 (regenerated): b0165d5eac6fe8273b6564c78e8ba548c97e6050ae785e9142de63c81aa905b7 +MATCH — every element of the program's verifying key is reproduced by this seed +and this circuit (4 IC points checked) +and it matches the digest you supplied +``` + +It binds the **whole** key — `alpha_g1`, `beta_g2`, `gamma_g2`, `delta_g2` and +every element of `gamma_abc_g1` — because a check that bound only `delta` would +certify a key belonging to a different circuit. `vk_drift.rs` runs the same +comparison inside `make verify`, so the compiled key and the published digest +cannot drift apart without a test going red. + +This setup is reproducible rather than *secure*, and the difference is stated +where it matters rather than here: the seed being public is exactly what makes +proofs forgeable, which is why this is on devnet and not mainnet. See +[what we do not claim](#what-we-do-not-claim). **The proof is verified by the chain, not by a stand-in.** `submit_spend` runs the pairing on-chain in about 101,000 CU — half the default budget for one @@ -98,9 +116,12 @@ make verify # fmt, clippy -D warnings, build-sbf, 261 tests ``` Nothing in that command needs a network, an API key or an account with anybody, -and it is the whole check — there is no second suite, no optional extra, and no -step that only works on one CPU architecture. If you would rather look than -build, the program is live on devnet at +and it is the whole check — there is no second suite and no optional extra. The +proving path is portable Rust with no assembly and no architecture-specific +dependency: the same suite, real Groth16 proofs included, runs on `ubuntu-latest` +x86_64 under [`.github/workflows/ci.yml`](.github/workflows/ci.yml) and on arm64 +locally, from the same source and with no feature flags between them. If you +would rather look than build, the program is live on devnet at [`8H3cYoiAA9LM36c…`](https://explorer.solana.com/address/8H3cYoiAA9LM36cyPr4UEv38dhHasSu2XPSdiBfyrLEa?cluster=devnet) and every claim below links to the transaction that backs it. @@ -197,6 +218,17 @@ So this project claims exactly two things: exist — 54 of 83 reached a provenance class. Knowing a member's funding class costs that pool roughly an order of magnitude of its nominal anonymity. + **The bracket is not decoration, and a figure published without one is a + different kind of number.** Twenty-nine of those 83 members did not resolve to + a class. Counting them as one class each drives the loss factor to one end of + that range; counting them as members of the classes already seen drives it to + the other. Both are assumptions, neither is data, and the true value is + somewhere between — so the honest report is the interval. A bare `ρ` has + quietly picked one of those assumptions, which means it is partly a + measurement of how much of the graph the tracer could afford to walk rather + than of what the pool leaks. Run the tracer longer and the bare number moves; + the bracket is what stops that from looking like a finding. + The sample's class distribution is heavy-tailed and most of it was never observed — Good–Turing coverage 0.65, with Chao1 estimating 108 classes against 23 seen — and that is reported as a finding rather than buried as a @@ -363,6 +395,21 @@ thing: a member proving, after the fact, that the **stake delegation** in ### How large a crowd fits in one settlement +**First, what this number is not.** It is not the size of the anonymity set. The +set is the tree — every note the pool holds — and a member's proof says only that +they own *some* leaf of it. The accumulator is 20 levels deep, so a pool holds up +to **1,048,576 notes**, and a pool with a thousand members has an anonymity set of +a thousand whatever its settlements look like. Proving membership costs the same +at any occupancy: the Merkle path is 20 hashes whether the tree holds ten notes or +a million. + +What a settlement bounds is something narrower — how many members share *one +timestamp*. Batching is what stops arrival time from separating members the proof +has already made indistinguishable, so a larger batch is better, and a pool with +more members than one batch holds settles in several, paying for it in timestamps +rather than in set size. The ceilings below are per transaction: not per pool, not +per epoch, and not a bound on `k`. + Two answers, and the difference between them is a transaction format rather than anything about the program. @@ -919,6 +966,7 @@ reporting. | `docs/MEASUREMENT_LOG.md` | Every run. | | `docs/THREAT_MODEL.md` | The adversary, what holds, and every place it stops. | | `docs/INCENTIVES.md` | What keeps a member in the pool, enforced by the program — and the one reward that is deliberately absent. | +| `docs/INTEGRATING.md` | Adding a protocol: the three action shapes, a worked stake delegation, and what the proof does and does not promise. | | `docs/PROOF.md` | Devnet signatures for every flow, and the rejections. | | `docs/CROWD.md` | Six members delegating to six different validators in one devnet transaction, and what divergence costs. | | `docs/USAGE.md` | The member-facing commands, end to end, with real devnet output. | diff --git a/docs/INTEGRATING.md b/docs/INTEGRATING.md new file mode 100644 index 00000000..f356e9eb --- /dev/null +++ b/docs/INTEGRATING.md @@ -0,0 +1,127 @@ +# Integrating a protocol + +Nothing in the on-chain program knows what a stake delegation is. It knows how to +verify a proof, how to burn a nullifier, and how to invoke an instruction it was +handed. Adding a protocol is therefore a client-side exercise: **no program +change, no redeploy, no new circuit, and no governance.** + +This document is the whole procedure, with a worked example that runs on devnet. + +## The three shapes an action can take + +A spend carries a selector, and there are only three. + +| selector | what the pool does | what it is for | +|---|---|---| +| `0` transfer | pays the beneficiary from the vault | moving lamports; the degenerate case | +| `1` invoke | calls your program, funded by the vault | anything where the pool is the *payer* | +| `2` invoke-signed | calls your program with the vault as a **signer** | anything where the pool must be the *authority* | + +The distinction between 1 and 2 is the one that matters, and it is not a +convenience. A stake delegation requires the staker authority's signature; a +governance vote requires the voter's. No member can supply that signature without +appearing on chain and undoing the point of being in the pool — so the pool +supplies it, from seeds only the program holds. + +## The procedure + +**1. Work out the instruction you want the pool to issue.** Its program id, its +instruction data, and how many accounts it takes. This is ordinary Solana work and +this repository has no opinion about it. + +**2. Decide whether the pool must sign.** If the callee requires an authority +signature, you need `--pool-signs`. If it only needs lamports, you do not — and +you should not ask for it, because the pool's signature is available to every +member and `docs/THREAT_MODEL.md` is explicit that an authority the vault holds is +an authority every member holds. + +**3. Spend, naming the call instead of a payee.** + +``` +mirror spend --program $P --note m1.json \ + --to \ + --relay relay.json --relay-fee 100000 \ + --invoke \ + --payload \ + --accounts \ + [--pool-signs] +``` + +**4. Settle.** Whoever settles supplies the accounts that fill the declared slots. + +That is all of it. The proof is generated locally, the relay signs, and the +action executes from the vault beside everyone else's at one timestamp. + +## A worked example: delegating stake + +This is the example the repository actually runs, and +[`docs/PROOF.md`](PROOF.md) has it on devnet. + +`StakeInstruction::DelegateStake` is a bare `u32` discriminant of `2` — four +bytes, `02000000` — and it takes six accounts: the stake account, the vote +account, the clock and stake-history sysvars, the stake config, and the staker +authority. The authority is the last of the six, and it must sign. + +``` +mirror spend --program $P --note m1.json \ + --to \ + --relay relay.json --relay-fee 200000 \ + --invoke Stake11111111111111111111111111111111111111 \ + --payload 02000000 \ + --accounts 6 \ + --pool-signs +``` + +The stake account's **staker** is the pool's vault, which is what makes the +delegation possible. Its **withdrawer** is deliberately not the pool, for the +reason in the threat model: delegation is safe to hand every member, because the +worst a member can do is re-delegate. Withdrawal is not. + +The same shape covers a governance vote, a liquid-staking deposit, or any +`CpiContext`-style call a program exposes — change the program id, the payload +and the account count. + +## What the proof promises, and what it does not + +**It binds the selector, the target program, the payload, the beneficiary, the +relay, the relay fee, and the number of accounts.** A relay handed your proof +cannot change the program being called, the data being sent, how many accounts it +takes, or who is paid. Any of those alterations produces a different action +binding and the pairing fails. + +**It does not bind *which* accounts fill the declared slots.** Settlement is +permissionless, so whoever settles chooses them. For a target that takes its +destination in instruction data this costs nothing, because the payload is bound. +For a target that takes it in an account slot — `DelegateStake` names the +validator in slot 1, not in its four bytes — the settler chooses, and a member +cannot prove in advance which validator they will get. + +This is a real limit and it is not papered over. What the repository does about +it is check rather than assert: [`docs/CROWD.md`](CROWD.md) settles six members +delegating to six *different* validators and then reads every stake account back +off the cluster to confirm each member got what the plan said. The read-back is +what turns the limit into a checked claim instead of a hope. + +If your integration needs that promise cryptographically rather than by +after-the-fact check, the account list has to enter the action binding — which is +a circuit change and is not what this version does. + +## Two constraints worth knowing before you design + +**Account count is the currency.** Every account your action names costs the batch +space, and space is what the anonymity set is made of. A settlement holds 64 +account locks; three go to the pool, three per member to their record, beneficiary +and relay, and the rest to whatever your action declares. An action naming six +accounts fits fewer members per batch than one naming zero — which is why +[`docs/CROWD.md`](CROWD.md) measures the ceiling per action shape rather than +quoting one number. + +**Compute is not free either.** A cross-program invocation costs roughly an order +of magnitude more per member than a plain payment. A batch of delegations large +enough to be worth a lookup table needs a `SetComputeUnitLimit` instruction, and +that instruction brings its own program — which is another account, which is +another lock. The escape from one limit is paid out of the other. + +Neither is a reason not to integrate. They are the reason to measure your shape +before promising a crowd size for it, and `mirror crowd` is the tool that does +that measuring. From f73fe7cabc5fb87245427723376d8e06e79ff1ad Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Tue, 28 Jul 2026 15:21:20 -0300 Subject: [PATCH 81/83] fix: a settlement figure quoted from the run before last MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README and ARCHITECTURE both said "100,000,095 owed against 100,000,095 paid out". The soak's clean-pool re-run moved the denomination from 20,000,019 to 20,000,023, and five notes at the new one is 100,000,115 — which is what PROOF.md says, what the arithmetic gives, and what the vault actually paid: 100,890,995 down to its 890,880 floor is a debit of 100,000,115 to the lamport. Nothing under it was wrong. The invariant holds in the program, the soak asserts it and fails the run otherwise, and the generated evidence carried the right number the whole time. What was wrong was a figure copied into prose by hand, in a repository whose argument is that every number is checkable — which is the one place a stale number costs more than it looks like it should. Found in both files rather than one: the sweep after changing the denomination went looking for the denomination and not for the figures derived from it. --- README.md | 2 +- docs/ARCHITECTURE.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 720a6450..1ed1dce6 100644 --- a/README.md +++ b/README.md @@ -909,7 +909,7 @@ without them being true. That file has every signature *and* the lamports, because "closed to the rent-exempt minimum" is the interesting part of that sentence and a list of -signatures does not show it: 100,000,095 owed against 100,000,095 paid out, and a +signatures does not show it: 100,000,115 owed against 100,000,115 paid out, and a vault resting on its floor with a remainder of zero. The soak asserts both and fails the run otherwise, so that table cannot record a discrepancy and still exit successfully. diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index de909c65..d31c1cf9 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -82,8 +82,8 @@ credited to it, and pool creation refuses a nonzero entry fee, so there is no second category of lamports anywhere that could be mistaken for backing for an unspent note. -`docs/PROOF.md` carries the devnet numbers: five notes settled, 100,000,095 -lamports owed and 100,000,095 paid, and a vault that came to rest on its +`docs/PROOF.md` carries the devnet numbers: five notes settled, 100,000,115 +lamports owed and 100,000,115 paid, and a vault that came to rest on its rent-exempt floor with a remainder of zero. The soak asserts that rather than printing it, so a run that disagreed would fail instead of publishing. From 4b50dbe55c0178ba6c3edac337858672bf516255 Mon Sep 17 00:00:00 2001 From: thiagorochatr Date: Tue, 28 Jul 2026 18:12:11 -0300 Subject: [PATCH 82/83] docs: the run tally was undersold, and the gates were described wrong MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Writing the work up as an article forced a per-run table, and the table disagreed with two sentences we had already published. MEASUREMENT_LOG said the honesty machinery "refused three consecutive runs, twice on the failure gate and once on the bracket." The per-run detail in the same document says otherwise: Run 1 resolved nothing with zero RPC failures, so no gate fired and there was no figure to refuse; Run 2 was refused by the failure gate at 14.94%; Run 3 by the informativeness gate, 40 resolved against a threshold of 42. One gate each, not two of one and one of another. The README undersold the result. It said eight runs "including the three the tool itself refused", which reads as three failures out of eight. Six of the eight end without a publishable figure: one that resolved nothing, one refused on failures, three refused on informativeness, and one invalidated by the defect we found in our own tracer. Only Runs 4 and 6 produced a figure, and Run 4's was superseded by the correction. Six of eight is the stronger sentence as well as the true one. A measurement programme that publishes its refusals is worth more the more refusals it had, and rounding them down to three gave away the argument. Also adds the article itself, which argues both halves of the work as one claim, and fixes two things it caught: the lock decomposition in the README summed to 63 because it omitted the program account — which no lookup table can resolve, since a top-level instruction names its program in the static section — and the Serjantov, Dingledine and Syverson paper is "Active Attacks on Several Mix Types", not "…Mix Batching Strategies". --- README.md | 13 +++++++++---- docs/MEASUREMENT_LOG.md | 8 ++++++-- docs/THREAT_MODEL.md | 2 +- .../a-behavioural-anonymity-set-for-solana.pdf | Bin 0 -> 425306 bytes 4 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 docs/a-behavioural-anonymity-set-for-solana.pdf diff --git a/README.md b/README.md index 1ed1dce6..280f39cb 100644 --- a/README.md +++ b/README.md @@ -237,9 +237,12 @@ So this project claims exactly two things: rather than in the observed ones. There is no budget at which this distribution becomes well-observed. - `docs/MEASUREMENT_LOG.md` has all eight runs, including the three the tool - itself refused to publish a headline from and the one whose pre-registered - prediction turned out wrong. + `docs/MEASUREMENT_LOG.md` has all eight runs, and **six of the eight end + without a publishable figure** — one that resolved nothing, one refused by the + failure gate, three refused by the informativeness gate, and one invalidated + by a defect we found in our own tracer. The two that produced a figure are + there too, including the earlier one this work superseded and the + pre-registered prediction that turned out wrong. **And the metric is turned on this project too.** `docs/CROWD.md` runs it against our own devnet crowd, where it returns ρ = 1.0000 — the best value it @@ -454,7 +457,9 @@ to build, and ten is the floor rather than the maximum. hard way: a batch of 24 was refused by devnet with `TooManyAccountLocks` at 77 accounts. `solana-transaction` exports `MAX_TX_ACCOUNT_LOCKS = 128`, but that is the raised limit and it is not live here, so a client must plan against 64 until -it can see otherwise. Three accounts per member plus three for the pool puts +it can see otherwise. Three accounts per member, three for the settler, pool and vault, and one for +the program itself — which no lookup table can resolve, because a top-level +instruction names its program in the static section — puts twenty members at exactly 64 locks — the transaction above sits on the limit. Settlement adds members while the batch still fits and defers the rest, so a diff --git a/docs/MEASUREMENT_LOG.md b/docs/MEASUREMENT_LOG.md index 7dfc725e..8c6ca7a0 100644 --- a/docs/MEASUREMENT_LOG.md +++ b/docs/MEASUREMENT_LOG.md @@ -167,8 +167,12 @@ extrapolate upward. **What this run does establish.** The pipeline works end to end against live mainnet with a clean census; the member-weighted frame resolves a near-majority where the transaction-weighted one resolved nothing; and the honesty machinery is -load-bearing rather than decorative — it refused three consecutive runs, twice on -the failure gate and once on the bracket, and each refusal was correct. +load-bearing rather than decorative. Three consecutive runs produced no headline +and each for its own reason: Run 1 resolved nothing at all, with zero RPC +failures, so there was no figure to refuse; Run 2 was refused by the failure gate +at 14.94%; and Run 3 was refused by the informativeness gate, 40 resolved against +a threshold of 42. Six of the eight runs in this document end without a published +figure, and the two that do not are Runs 4 and 6. **What a publishable ρ would need.** More resolution, not more members: 39 of the 44 unresolved are budget outcomes, chains that ran out of depth or paging before diff --git a/docs/THREAT_MODEL.md b/docs/THREAT_MODEL.md index f6f71e48..5a915596 100644 --- a/docs/THREAT_MODEL.md +++ b/docs/THREAT_MODEL.md @@ -315,7 +315,7 @@ size. **A batch of one settles, and executes.** This is the standard trade in mix design, and the standard analysis of it is Serjantov, Dingledine and Syverson, *From a Trickle to a Flood: Active Attacks on -Several Mix Batching Strategies* (Information Hiding 2002), which examines +Several Mix Types* (Information Hiding 2002), which examines threshold, timed, and threshold-or-timed batching and finds the disjunction inherits the weakness of its weaker half. The argument for our case does not need the paper, though — it follows from the code: diff --git a/docs/a-behavioural-anonymity-set-for-solana.pdf b/docs/a-behavioural-anonymity-set-for-solana.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b334477d4a5fd960ace12af7f6c54629dff4f382 GIT binary patch literal 425306 zcma&tL$ENwk~Zk?*tTukwr$(CZQHhO+qP}n=gjRv%pcLc=vq|Nx;FJ>=36B4!lE<` zv}}+h^UEXakPJ+W`1JU8hL(`r+>mtACbnkI=JC;bWyKZk;_~DL+|VM{=6M4)WJwmOQBGs z`%0m;uEpxA+$6QTvV3XO`bqWi^=x&?@Ocn>Gj@6PLO)$_nf~5}`$=NiI6)@;b~kwK z`+0ueOUqN!%;;`Cx!geZsIp9L!xPnDajtc1zwg460t!28Er-5*sJ2PDN@d&Bd>W;* z-Q>;SY`wkxEKy1>w2uZb^+hYtq^KiXF-Bj(SQ@b^t^BWPec5fiLKW?|t??oS2ny zK2rf{WXU!dICYsY>&6b^g6_UoLl+3eo^tHV+hO-$(CAp?w3|wl0j*otoNu4J5~b!z zXm&75uKo4+N7L!em2C^!cN6NSrW(|7wT;VSE`@MwqgvDL(Ynlpu-lp{v39zo}Z017R`dT%$zZO?+4L!H5< zv(C74@HFrh*JwC)idP2jgRHw5Lkp!iVd79c*4`JX(qTEN5+IOI*(*7GMnv-Jol)7* z7cJ{GMOx?l;Cpw~R_o`7?=Dz_nkD-iylFEI=-aJp*oIok1*71c>E+C(dCKYDJZjo# z`D{4u5b0OKHmttu5A?0)xdlo(+P!xytOVIQ_PW{;hyxYGCZZdj&cC2&%#|l-5zWzr zFG{Fn_ili$=b#!0_aUwjy-mTm^dK4jEHP+tp3Z`klkE_XBy5?@aqNDoF8kbwK1}Rq zk@mSpKX|kRl1iqTvWO@r3WkcA0jlCz0E){Xr0_dvlijzOY-hN6oPq91&}yy=|MERw zG{s0a03a?PX;exv0x$jo6bH4F0AfoQweT+91G)@VK@)4JK^ip_Asi3}nvKXAlCW>5tl1q z*8Xor(rOq&e}FdHS8CU%&nAQR;;6sq@3M^v4F<9c4$~?LE8GWmPj}mQ8+d%@D5MNv z2UWOG!pzYKHj04e@0FGQ)xf!oh0d2N^lLoPQ9J(8WfT@{GAh@!pwxyyYH)l(BDD5HBa zHN0`8xfFHfRY|8x5?c*1b=v`v>7!$gMah~ZMeaXq_POI`QG<>QbZrb^QqHT_Zb@C_ z?B%Ft(Z$MWAdMeC@HM8-K2O zSpB#N59^{X9ENWK# zV*PwFt_{x`nC79EyI0M57;9e+IdrWvBF@v3yEQi2)Jcr-j}qmTiMMa-<9U}wo9Jm7 zg^{LNJn&com=n9p?+GUx6Wl(MYsc~K;6uU8pbhxoFP>}9|J4D3- zEq`Y&a-RgUoZz7=Z&A?hx-Dm48ma}l`0xWNBs(!W#qO;_OtIXYdg1OU>r z>=SAa{&2x}{v3_O>rycT6U^~9&BSF|f-#_z1WbA0eo5jg)qJd88~4TU{4_JsK^3Wn zq^Df8$g)pR0d#F;8$h&si|2LF34)+@&ABD}re`o41nf1%j}nKYME6lcXSL}HFDuAJ=Ebh1>`&Fg_H#AGXMoD*BC%i+o7Vl6$P<+ch*Q)#(?q38kqte*tBFJE z<yB+!4OhEDNde6u`ixF@3gT^v`Asy%L@CkQ;G#tM{P9ulO3%S?Hi73C=)jEd zBW<=W5IW5v%X&Ni__W`nZ4!trM9PPVkYarK#;m}2h)-9t7iCQVX*~^1mGJ@C7nch|n%Eluzef4b>A!r#z{2o9 zNQaH%e=VI64Jo@L(f^XpJPorxlkro<^aPN5pmqF={%Aj7#&v;6D{<||d}1=oY~5a6 zXC*~~kq4aDdepkkPjzK&9WJD5mrDKThYtQ8T-=@SH+r3#!FgSha1Cx-)jXVF$bddd z?I8Jm5VwYjzgGxAd3)uQXrj zijBeUQ-50RYR#(#I`Pc3ngL=Gy?t$6puJ04v2U?`0U{ct-R+<}$u1qughTPwffjC5 z!(fjtg2jH_(|SWWAYW{e3g)nHy=?l(m=B9S(lsOcZU|QqZVYs^*APs7Lxl`e5$JA2 z`{gGY=~d(P41M~6D+7#jk=H5_+|QResGFVFf&^N?qmwq*#F{T;#d_3bYq3d!S_}}| zsE-{qTiDU*tj!o1m7C)+$~WU^*8Ek~%mX(Wr(NfrcokV&L-lZun61m!Nc`h}BhQ`d?bm&OEr7FYZIhVC~% z&8|`+sBda`%%3d{a;ik$Fc&tPiwgY&rjFkf4k|Hb4~0KiY`K`OT1se&Dgs#QMU+g< z{eYXxe0qu}5u6%hP1wInv|On(>bo0fAxWB$<{@c5f|9P`MITJj{K#= zc-zc6071}UD4_Rd7~dpA4DF+OHBs8tv--hFmkdy9nJ~KzE0z2e~Kt5;m zX~vcUX&3*x8?N-)m0-}a<{P?H%z7ICJ)Rp4R^G!DoO=^3}CU5jI3;gREqN3+nt9z*O zzcBeAw)K)3`0}cUKON}ji0jyaBiTw**lZz0&)s)s0J|CB{kw?UBR_BrV_py+8wihO z*xZ%mfZ8(jtigNuChAe5G3Gj3HiyDb0M%pK$-#?!2X7luoG7DPfO)AmJKArNOShu4oe0?L8-?#_7@pzGYqLniK!-# z1-9CV(NXF-CByuMtp`kd;^23K_OhBXc{EK^>A5=!Da8d*lO)(rP2xytvXBELJs5s-=tX zzYIPTit~Y$Cb+e`v!Fhv%dk39m(CV8%^BVigC)4gg8Ey6DAe zm+=N$SXBAJFWRb*58x1~0V`<3NxH1v=|Ebi3J4c-2?0wPayoweb`_r(`NQus{3e^O z>rhTOZF)@DqLBOM)|_+m>{Q*hk6Nw38;v?b!=92^2TyAH8W!p>N!%YlaUo;N`d+(X zcazZwZ%gtoED2_0uTvk)EXYEk67;xw0!*SQG8cF_Sm#qyx))5A>`kt?2HDm5EgsI2 z4nb$RKyTI!+U&wQf2C;|RyaMg^!=*Rbl@KAFC@>Vd;I9`b5m44#1>?PFxxm-Y*tD9 zNTAZ}h?L!1QH8Q>6h0A-097_`vLO+aFIbM{XNN81KY@m+p{c%3%>o>WocXs`nSt_v z$%F!RE}l_brzryMc0s8jvVta7d;SkGzqvE1G1eW*R`$h}@%iefOs>!GU=1hmVT!PYXW=t7ol(&*mDxvcvDc?)Iy5HU^`x#u z2N99pEa44nx3T86@aJ{K?+Xe2`Q+f~@9DA0p43q#;JjLim9MPa{b75MMG#&s-RYgO zrH_h&Qw5~VJ!T(RmE@gz2oBigbiLhXH}N1WYz}U%B`^Hn+F#W_ByjnVI*9o*tzVe_SeFT)(NyRv z13LB#RPzAsqJuot^Yi|-6hpm%F?yk6BFHrVm%BpOc{UD83Yt0BJDS=kJI<{wMk8>e zb~BRQ2&t&{>SNO0?*|ryGYAn@5MJfm>(X=|=Zv9h_JCz?)#339NA8h-Kkl>W*I_c> zH|7fu`S5t{hkI2H6VuLv<7yX%C|1(Bp7s|7fUbfVTm%%xz>3fix^6B3#ts2hC&06; zKxU+`c>kR5%$6tIP22b1Rj$_6=2ti$jYu4Bzw0u?#@zk@B5=ZD{s%X+GW`#3X60b} z-*yONG-Q&t+7Wub)b7hGFc*L&?umGdSu5C8DmJBDZFjNp5fL>?#Y2istAAcRVr=CJ zZzeto{bTAt?>}AGkU`4uA%8yz5+Cj8@OXV!9rBgcEtBcS@FAxFZ4~kWQAP|hNd#~! zb{{ zo+?atRd6olY9usOh%}AHrkdOgo7iyGeVdzZ?Wk{OIbe(bvHi08%%qHt_h)E%a1pns zJLLWn#>D8b&cpIRm9_TL-M-eZ@Wl7per(G{^K3BH+^FPbzBcNT=(W1H zns&yv!g?%lFLy$}Qo<}DTdk;Hjm&XwPGmL|W?-d$zgd#?g?PJRcgDtnaE(|)ZF_$_ zG(|4boGbRt5ko#I!%??K##yGWYcIJOge$BbG7!1 zOLV?dp~huV_J|)ycc}*EwJmdWlAr*WgmjyJ&&51iJz^0yJ?m&P+nU>Uv7Kx8cIx#$ zd~F+@*5+bYu~7S-sUME7_!(+6Xa5zK!#N9}eWagcx#G~F;)ETk4k$b{a> zrKZ)ms^c~cays34;ksELvq z`e6L&ng}}>H!<7GJ76m9=E4f!c<;m|+f=l?9E7pxsgIZfp@pCp^eh7&tVIoFVwDmi zacN`Au2EycA2=Lb

2R$%f~=>sdGFL-11Jo8&IsyGHW~&%vbwjLcO}_IdAa#@lVU^Db8% zGIMIF;f9Wv>za)0<2)nXpckSZkRt)xw`o_5Qg83af z#VWeie%2tA^~vZBnj3!5_n=Sxy06tH&;_Z2QumH%ZvVd#jG*vrim}MGIQSK5q}9TR z+>U@b82(K-o-nC%ZKpH`!ZS_bE;C#j2dG%D{8zSL`-G_%TodwA%Vfh|1mucqb|g)T zbC1DPyX|}bJ?AbY`s~%if)MPa;hlkt%fnt`QY@Fy*w7>PY>juG;&PpAO=Sf5T&*^Rk0)TBu9A**qs|4{SlLN zlfi?t0Udr&J(hjT4^4{wT8+zX@6$&ZOGD-atYihp$c)_&NAv?Gj^rtIHFJ*+*Wowm zCPv#-6jItR179_xVE2Y%xTF@ngQSa7a*1;-C-W{>0m9&DI&{mm5b6VZ&H{2Aw?W1> zYGE5am$l7E<-ylUZ8E-Kc8N+juox?^X{yFzaKs!gyffEds8wpjJQuFcOLgL~C``pXvSxNvD#TVW zHAA9!CH^Z(R3}{JdLp-<)a4R){7S*a)EX1<`z-uS8Y!2s^jODP%R#)3d%7>?7`HxK z#bq+_lzN5J^>C6`V8Pp!F7X`apXSTc_Dq^_jS!^4OFU>}tWF^H0$CDob%tnd zS1Nr$SkluV;%N?Q`=5#0gPTNq57K>EM$bU2gtZ`pSC041Pye;b&c?qhXpJ3u-#pTY z)qkqWsc;YKg@-yjv8|2?p^vzbi!>@-qX!K$v110@O~V49Jj5ig7!8HwmYHK!eog#p zdbMHletBee&9PGTy|SrIyfLTO=M0a}=n~qC2&z4KCejB1mjPo z1|GH^=_|fXD=8Z^2jRy&+5L4P4#WV%%ekI%5n!5ktp^J)8GLrT5vjIzGxHGCu%^D= zng{WoAOq7+8^EeJ)qZGQWsq0Xvdgmre;J#?_84JGTS z8t$jR!eAL_(d*lN$pwHMjCFj+QwFqVeF3{D`QVM-Res_|*rTXj8{RJS&Th)P3=xKa zx-x6Bhagh0>w5(Y>>{{C@1FE5Z@Bw;=8KQ8ZOXgG?vKsS8~k~``(9f2A*58IK=G8__F3!&c2@R+`_8%iyR>c$ib5CWfG#sH0UORlNh+(qlq?% zkBRqV7_7E|JX?UD;f$&tTr5& zez9qY3usvdG#<~I^VtrPCim^Wfu`wbQ6q<2XIDXlX@3}5;LMQbYtqJ`_<8PnQ9!gx`xEzatEz`mMO+fEF>#`Rr>JRd{ z-FrbONYtiLcz zCClA3eX!z-8%sSdgzsA74B5P|!Psey)V2-Aj(ch>YMj;w3gIN%z`>*!jkut?DN%(- zT4CS=`52PO1`T%Uc4pi6ElyE1Fov8&R7)rLrV3>E+m2kWmOjU&!TkZrkkfzv53=R> zpJdC-%=EvNZHmTL?0;t8K6!QbMbPV!w97PhNh8GTI`~+|m|g19uL3kntPG9D;fin1 zdv_G73uqlV4!YF48q}&eoFZaTJA|6wj|7Q#a&maS?;KL74%n&~y>eS3*rE`hg(BVI zOT^JCE#!shdyWsz>fR8bJvr67wJMpwMsj=X?6Pw;4PLg@A5Cj7r7St82wMew+AeTP zV?0N_xE&TjD~W{jsa&19n>Wtfow>f~9aUqEe_o$p!q0)C1`4!~jBrrmcgNpElZA7g z7&3bc>u?EJT-T`D?>}j9QFK?#6F?+8)d;y5VQVp0>@ZD@gK8m?CsAcEqZL;IeBaP2 zKOKZmgb+#8W&nH{JH8T-{TYwU=AW%74hm|;_)|PDwX8WQ1~nc95}8(D&(4~itS{n+haqBk+s51r1te<@aR9<0O@gB^BH zoj=gr$H5B0MrhuHVev#zPhs^!11M8g*Wajf_B`5oEr^P~?sLeq61k z4z6LDD~kFkruVb9JIyT|>u&)8vJdp~rv{(94moA0_XVXntfgBtJNgXbjgZcdAfLGC zH#m1QkiwbFM@)m^qGulph}fQ7zv3gxs-jrhtBkJjffo*(j$G9`m=NsPs){2RO2__Z z&_ZqkJ43Z3J~gb|2t-i}+Q;TV~Nz$w`P#u?(lp`Pe6OOhiu zqD7%PI~b~HF%8@nP{vm~EgSJ(y>@u%soU&WHY2$_^wbLb@jVia9|wiR)KV?uiG&3IfoJ)DO$&!%D}TXQ>XI9!oV>wYGcJeeXTF|E`OY0xm_b_2+9A=nbb_ z7mL=Thz@_U?j;4SYuTCqMa&>cSj+R1~SN3rRj zz>!eu+td=GIz1tD!>j#Fh+f`Boz@>X$pK~x1A}U5u1}>w5m6F6u0fatEhGBqFl44q z6BUYlHB8&PT@TSEWahjBj(kU6ay%SLrB6V|$%eK=mQG=Y{*xUEH3ur~X>yBG!*3j! zd^%Kyxs5!Mlm-Xs1wq~wp>AfvDM8e4xD$s_{`dtkkoR>GFyBVsl1`0)sH~GVp%uZ{ z!MG?p3-#G))P9r8BoI9f#GYHinwi4cfyeDQMCF1Mh~N<(1;*?T-u#016y`dX4yqO7 z>pi2ZMG(?#VvwD0Gsyt0+7OmEW=jsp*1maz5Bv5)MGMR>FI3DN*aVN zSgJ53V3~QCk;GHPp*JGCN@`?wiKPHNuS|p;mi75$pUon{Et(iPasouq8#a1Ibq6UbI5KCd>ri`icSUw4MD93UwX1@>0BU_T3oPE?8Lsr+yrb%psPAdL{ zs*{wE7uida;z5!*m$;;igs z4ulM+X-T^Q9Z4dDiDKkQim=l(C+FKkjysucdz-NWpG(1prBOOdD#@`6Y?*zeX$O{_ z;Fkbz$$5d=)CI;w0c1kLGgnSOYXHXdM-0o{JC~?B&|kcqk%e7{0J#T&Y9Pm5K#Ta6GQXf2GvTHtDV_T9F+L3HM&sE`cXolg;C-!I^iAEEST;c*T zywJ_du0aKJb2XZ+D?#?kt-(!4;F5-vWAL`!bx;j)Ai#K1)7W3p3ErG^NyCTDZ z`}wRbpg=*5FwW{K;B6N}#UBy-#m1pj3i~&{QQMebv9%f`KQ~t%l;_F-1|h|FQ?3O% zW$mlU8&~|6;RXT%SIVb?STIEQ=qonS;TNZx($(1a^Xi6Io+3xCz%CU7SpboS-l^iD zXf}H(j5e>8Etm=~8HzZRSSTEiYhp~psO*LpItYK_s`hrZLsXF*wWoqf`Pc1*lf}p5 z^UmiubJhLdZO8I|^emM<>`n0LHjO&=;-8(&(6&9|EC_AIT-)9 z@*B~#vD*?w{H@h%*sBH6wA|B!6$t2Fwh4HJ9|mxAli(d^ya7)j$q^r$=<72h&s>g_ zQglrw4euas$@V$T*vQauWI38<8qv@Xgd!zIf{Za-)FP!;LR5>zFiSt4hMct@0+}l| z)vbtNmIe=!ieuep7+-)d#uumJu1lxaX3T~R%9<3Q6gH1tRWBvrwV!Pkt(q1#!WPUY zP*nfMO;>JbMWm^P37rS1FhBh_>KD-0JQ%|-0K5$Xw29I8 zFQ*}ghOGo9MB@Z4p-PRpcNP<(svrx*GG(p@zs(r1V~3E3E`q{mqMeTjjLeqJ2T~j6 zOinh&X^aBK14L5=O7gd%7R>=yAJa?(dQz8i)W^Gu@IE)^X-2D$hoP_g$38|lA)MlfE84_OJGJ~a@HR1m>fHZeZ!;g49K4;A`}UJnL`u*pb=lEeNUS%fs_U4lP- zLBm-DHaZNak9?Zd&&F@#ul=sPpD4>ui4#nc8X?IKl=Keq3y-V-5dBx+d~;bvd>xVC)ue=A6#b$h&yJiF?~RpaAn7YqiV0-18F&%Yle3((rGb| z7L)GSXSG#OQirY}=d$9p`OgC5dvSgZ_Nzq526wJ%)uQwI*?cP&{bN|=dYLVzB@JiH zyvK5tzFD`+W^VW0!b1L+iutplLOfABF=_n|R zUA$$p*6<@m;Rzu{M$TNK<9;A`Cuj}NT_x_@#(LHTs67Ye?Xx%h?5>c&n58^RGGu1$ z!oF3KeNCU+)BD((X_h;|Y!aXgkNurGcaSF1F&Nc@ndV9KlQnj4r~XwPF_dvYr26Hg zed(qu#-J&EVocGOlN5%4W-``r1=}iW@TS^7@>80R^(T4w)psVDa z6eqs!kl&T5a0Eyy#U;B}>`_~eXJ@I)3}czDHT*~kgX>2IdvFSY5V2wZ4RlDyY+`S8 z>*_f;+^k#@(je<)xaq)?z2rx(Y`p7az8KeF+!cc}F3tKwpXP7rsOV2%e~TE^ zT*Nbh>6!AF_O<^Aa(@aTb-S&+o;9MANG_>O)@rY&^A=V&aq$4}LS$MeY;|bO`X__^ zfQ8PG|0(^%aN6b9niIP_It!+jP>lOd?}3PqZRLlNHq>@BC<2ETE>^ehh1!L0m}R=m znPR?nxVPBnnA5edXVOHtLwM!hMp}_{^`ObQ_PX;|prkDv&Hv=xaH6y_@Tl5-+c=7G ziqmE!h|Nrao7;K%m*d=1x4ZTTyC<5=aL%d-*C=O156_cweSwb2?&1jFtZx0;TA`P0 zT`pI;w64$Ek=2KzYnE*3>`Eo0ZZZ&pq~gr@lag+=ZOcPfIS;N%%kLyDYNP4J>@~GG z!(VevJ)k|aK+lCOS|{(j@m6JWD;0S z!3=bM>gCm&DeFx7Cf9?5YRbFI(9!MV>g=)Y$x-#RdeL8|)NDQpfy<2ZUqyum!*j~^ zuHJl^lA7MUnCM9r6f~DlhtAyGQ)17EI^FF0v&(OXG0Zn}k*G8J$^k>PY+kLZmAJQ- zPalc+^M~u&WT%Qt)z#{9O^udX8&?W!yxJq#($iK{W9P3^!zX(Nua+I%lTAo`U*0e8 zkJ0CMO*yT28OQj%uF32InQdc@A!qpXe4AZdNDbJlC!d8boMj-FJ+EBUUqAs| zvWJ7zSR9loE|ZzafN_KmibP;MFl1ho?;8Fu*)>JMt4o|V_-|)N2u9X@BW-A zcdJ>BZF$8-mMMTnc;gX)eihRad*8vvNxk-d(nb$`w`!wvC&ou`zi#OR3h`o!ycF%Z zrj{$`MMP<6kl&*ww1lSRtH-o*2eo%Tt-@_MHVH+$_&=4{)YPe$YPpR1Rj z6w{sk%^BaVgkNgKBklo9AFbNpm-n1jyz0 zyxvmS-0i+0DcRzOPJq0H@n!<2$j*!ClQR8WX)%qvB~W4CD|E)ERYI?4sEH6T<~r+; ztsKsbwIMjZA;c*#RA$*hztnRnGnq{L&&*^>!$GsmG0V#&<(w{>G=~|Ht*$fAU?@<~ z%l9kc^N{evjof(gFlNjcBvE~{CP%1N%|_jj&6yi;oiru*zak_s%lfNf%Hp^8tGEmxa-7VdUQ<~5--{9Bpe54zh1lK{fNW<@ zwR`Y`q+qwOPMi(@fZR{6OUz`w>7p^5qij7J=S@JH-g zMYxtO98oS6*=9;-tQl$zjjQmQZ)8Es1q@4D(&NI}NrJq}wDdg=0J;jo-lmh#$LXm8 z>c&E{Y_r|!(XFY6-hU>s1D9R`2&S_mbiUf(laYQ-8sBTjNF%=4wHhE%nAv$R!>i^- zhR)1p&d{r{bKZ>%fIxwz08vm8W-`YU7~C)ft)!@0Hf(L8xi4qv@$jI-x&wMpvqZti z&#@mwg7!58M*tZ-+tatK%I{qq6sXYgO|sp#>>7{Pr#K7ay#D2I)ma^P_%Fc~jQ2w9 zJc$u`)fh^6Z=;lW)d%|oFPEicklbD4Fa)`@KA1lA!+UJN+3pE-riUeI{L3JDX1%pT z{mTqQ91KJ))8ZKz6XVg7*$$Om#~g#`x)l8YqS)a)cN~?Iv1r zvbl}1-(q&P%+G=&x#OD}0fjUBteR`-DbVa6I>+|TG5IIqXR z{{rm9Vcz$VjEtaYSsPm*RkA3UjJWGL*YbmC9Six(CzCkjb7`0FnZtp8)@tKYsHF#9 z9%A^oz*=xLV{r?TvUJf%S}e5AAPW={63;@(kgwk+MbD^`dD1IvKMVr8tnPUxG2HX< zovuGDTzNqYXu?R)=EngQQhjjLgmHB(hmMnFx#h$zmkgY zVG1P!eL;hQV>dnKTJL85!GyfTiC;(l=OIbR#t=sj7>g1^uZILO*R|;M72BZ;fX)>F zs0I+&v&{rjk;gkg)_lVt+Z7Ora4B^c( z+u?)L_?t}V)qiGLOux^OBv;lh#1?ij2HSRS4tVV4KB;Dp0WE}GqMJ~l z8H6WURKXTaRA-_@N$Dhnti!T8do+H4(mcA2>bw-$Y2?W>TRa-(g%Pq0Y%K&((`sXI z+0Bi}f~BcAwf2VWOI#QtIgmsS%d-ocYFJ?*l`Lc{XCHro2(Zw zVE`ZrqBsbNjl}NS5{FrD?G`<9;X*VmT1RC6YxGh@Nx*m>3-+}WH$Lb`b&z8E+=?Ny z`Ss_QR+|prL)pMu%6P!AM38S(HkSI0KmZL;G1Cu?4%vki-+RYQ3PfIJi!1@C0z4m~ zN$-FqCj>p;9#8Hiv$061ZtVo!kT~6%de!KBT$_Esmd_wo1b%Ai_CNK{?bNojExdk~ zR!i0?YBC6afo|2`Ki4nD6Y||uQ4md->h=QHG3H*ZX6TjgS8iJm(Hov~ zO);4>rE5to`UoDZ1y^1Lz7F}4v#LYs=%8(etmM9!M&5KR6fH z%2#BT@u;Ph3GOekpanS=6*-c%m;Xc<5xOX{L)aI(JH04>`bqY@bZi;=6am1Rt>n(E zb#q!L{>77vgmEMn3LRmHltEct%#)yfSQ5%NjtV$jl%nqnEtwtZhjkkBT9qA-N7xRIKomsGmH#m#{02GU_n_sN`$q5F-x6%K`1lDjI}z$_oUvaN5E0?Dco^ zKPuzuEHvmp3U9Na6-Ws0j~c37Rdek9)pp8WPp!G&V68ltaskxws)ZyCV;g)}Y{>lO$F#CQ@-CCwFLGx7a$&j$cz3$d-42m+sADA#V>$VN? zsX{W7dd94X9#DGC=N~5Z`+aU>KV$MKIK7(rCpBpURULaasQR84rk`71mi;Fm3Wn=g zqx3)UnTg}S^O=dBf#ZM6=N!z5#A?>Z-`+x7p)uttO@4lM?|VkwQP?n}Z7{KJHn2{4 zw<9qDjB?+pHMvqo5g-tq*bZcT6Uj**g^8Ub*;`+CqK}W6oh-Uuxo2651hV^XuXTw8 zGY;v=oK1=fk<^hcWy*p>Yd5#;o|xEM_oAlr-&0gy9XBVd=txPo$2}W87#(URq#`sqjY|rt2*#R1rL|piES{t2D~sto zWO&}L*fpQMX7Kw0*OMZm{DTVgQ5fC@EC3eZ`8KiLcGnRgAZ~Y0d2lI5MH*+r4D7zv z*(VY%dwQbPBkQwVhglStx&3Ez@S@sX38XGtzKm)AW#Wj)&+GFhXC0B{H0JyH7BRv5 z?5C@ipf54Gr5-M4+jU|h36auL<@|FA;CsMr-|I*spK0VWJG6Wshhw(woLyKEAiQqn zfUsX-yc>?|;AFpYSlRivy4paLuC$h+j+e*%287X3XGhd@hI%(Pt~8A%xcQshfrI&y zewkhIK7qIPqO`y7d(*eop_OEAjit}`Bj-`jq5P3`v#(DcJSfpan4IP1+i!$LR4 zgsd-N%}Vcp5cJ5HXXi>PwMEe(B<$Rd#3~3iLn~KRGeWg%4BmfK2x6k;s(2Ov5Rl8w zW0e%4l{TL|F$v5k<$#Gi;-7~C#>&pIy4@PNVNME3Xz}I&`_A!I{Uun<<6}-Md?;@$ zMcYaDJx=P9iX()wFgYFUt!JPHU=9V1nvv|Fm?g-HWx#%JNg3xrlL0Re1?87cV~gyq zxuJ^+`19D$`n$}j8mSwNUIYY1=5lo0bmch}LdBOP#vZeYj9SDePdoyw4Pp;)dN z#u4Iy2D^Dxm~&N*j@gt^u_In}sm~yjzQrF-OC3sQp0CH`{{)!$c%ti>=^v=rZ#{~- z*9O3_7^K~`0uqE4T#7@$SC)u{y!uqq20rLwSR@GaGlI(>ut*}|5~F_!As8TazjK*I zfQH@XWB&0=E+V4Su|yfsca?1fo{GkR{yax&7W`j~y5Hg?;#ZQHhO+qP}n zw(Z@vZQI>%-@LqcZ}OeooMdJGUdfypsZpb6-#%owS=4OU{J3B@{o3GqX6cQ6_TC(6Hq%u``bLy4CSbaum~q^mY%0&Gco@oAOnakEeX3JrPW-#JaJ2ig}Y&hDN)KG zh0Ys)xw&jHF*tMNIqJIUG+RR1==e~lNI*{gjz& z?5QY;Q-XJp3wb1YD+MHWhxsF%eFm1Q){~0Fg+|+Q&r=srFouD0KAIKMEZtHP`u&`E z($g*sOv9uIVsq$G)0`55Tj?ICK)dG(GceP|6%#sLqe4~GwfO1OZ;zm`eewp;n1e#-lf@mnuJ=%(&}7Y4-c3DB8dyaUV3a4%kPrmia45L=)qDOjTX z6ptyN0|@q@oRGUfhJx^5H|zu;DeR3f>kyN>f^K9T)H3;A7@a_E5ux!|0Vm*+>C~cw z7rXvqyR0yV_$4}X<>x=g{$bkM7pdcR3Wa%(H6S&h?=t^-H84@l!bN>eB!Aj3!v1j; z7k}L$s~zm_%1GI*F#2~jH%xX}o^=YWfR022?#U~y_dZ>+K{b4z{DN{imPmyo?bv!?BkfhTVZ{c&YRxG4^WkHCLTq^vPswVxDwG_kwOy#0GrGPSY2q!9}~#}1s}+YZf90Rg8}Ry zr$mhnSj5OVCaq#5LX^K=zbA8CXO7W^0!EpGk+W7ok8cv%xBAWfoR5$LH)S8QQUsux zabXq1{Dv>q`|InD@eNNhO${m)K-aoXj(4%Y7oA3^TJh#Ek}I7E32N)-nus6hofgTR z0)>FjG1<6XgJ^-Pgz4QbSS*1=kn}!Dq>)V0J2z0)Ynp^A>KSSGuVNut~pCV{ne-5*6sSWzbj2=MBt{!EH#9Dt6K8an-TD!T2zSWsM4-43j0#a1kHeT`95$8J&xBzgZ<|%#ZD~qhM;(Ur=MB zL=Svc4ObFG2?k`oEOa3OId^th?h0y&y8`#UB3C#1{ja3pUbn?tKOJWB0w{K3#ESA3 zTQ*=T8~N4V7B4#UD8C;nEGm>?m}mNpt4J1lte_{BHmIL2F~xK`O+y|P)Lb4cb;awp zR&BrwiuXt2Ga+EKo7<7Oe2y2NRk)A}R)FA(l)|dLUyf#67_$X@c;vVnud8iRZz9UN z>ae4+Q`Tc7-{)az1)6;EecX+u@~+wVjI+X5vOOlXwRNQC!c_o8*Y#f&($4Yr9NhV! z)w+61@VJuu!vcR~-i)Jas?MsMqpk`xeRLi*v`}vA=~!^-GWw+Ne9cAGQ3#zhsv28C zG*%oBI=I>xDW3(9qojrslq2U-0U706x?dX#7?iAkYXtL>?h^Oc7mc}z9P%m!xvQE| zdOXt#WX5!Xg!uUE@;EvB(khcMF&c@QZdK|TNn;CLr^;z|BywgljzIetRNc&8kvbHX zVRQ=l1KvovT}KS3jPPy%K%9v#Bh0xK42@kD>#zG39vp9tDe2&Ndns(pStkuZ1NQ5k z2C`-73`b3tH1~+-d|7Bou|kd>V}Zn zmJZ8ADJJm(NZ)@7DS*FqsA$@yKn$+NTZyGPkDbQFMv$I(Ex0EPGsa4Gm-GWdVO_F@ zJn9*3FT9qsg^#KCy`79NB-@`k`OMMBN<6(&+}nN}HJA7K)Di8t{WF#aN*42%`@1!(tcF z-kd1ExdqwHF6MB`xj87l8*z#VQ=xt3d(lidK# zGsz9B#*6Z}`gz?-%;g0~C@)oh5N<+Nkfu;JaX)iVeR&ijRecB8g@G`YI`ynx}Ea)!| z`W^fL5sV#Jp&PLe?F^6fMaRZS6X%0{s&3x^4C~H8180o(tn!}$NGbq?nL+B^0yb0KMS6ruN0?GCkGQkb5} zoiioqcsTK(F{QCpu_Dp2KT!w~6`~$M{&Ko*c6E^lf1Jd%NZXMB?D5Rv>G9IEd5xxU zWK%7Hdv~kn<1j8lmmCEZGS_WW1Q|7C(6Ct^8s(gR_D}@1LVo1^;HLf!d96+=2h_D9 z@W!EokrWE<#rY1f+1NrS&F43B4F1znX6jQa zP>3s;1)idedIgdh9Tve%uV=FQIoo{-rw9p z#LPndD2kmK%y7rQ%dJsL&eccLMB|P_vJeZI7xedN{pvy(_siAk6TXd3-Z=pAhK_ou z9|EdLT_znSCt0mCd{B8LDkXA=UDo4z2HMeXN=Ao;ux~aY!tD=RU5l14+^6KxFTbL7 z7hMsy$+oNO=7&+xs_6Dhqmfo^SZk8qWSRGn<9)!M?5TmS`3ao$*(T*e{90bxO@W=b zvYCM;qj@EL1sTe=>Udk#U6xDX1Nq;F;AF;qvWR9r{-10wx;ca;Bh-qOoAd7e2t_oJ z`fGdv_dk6JhG*-|VU+=W+lf{2%cStIrL^O|9V6GlXs{*e>|G}%*l(IIjEtbCX$sCK zkLs>MBGI8=Kf1BXjSWp=XyF^S^;jB~AAq87!nnMY!tt&N4Det34A@z@r1ka4Mg$lq zrq9{L%BjH&gHxabX~YE&AaEtL@Cwu;HkS`eS|>%A>2)$wQao~OHF;^Xt<#}o`4eX9 zETs@dXBR7Zy86q+q)T^cQ9>ns^@ypYy^@QUFZR?FTFI|1Js2rTgTWGoGkURg=_w#4&`>gEStmJ*iUj`hQ8HDA9JIT;M+6h?VCx4ouDtJfs-Y>c&7 zx_i|%4wKOl6b{AWo>Zok{W!5DCy$;Q!E=1Sf{!%iEU-)E`#wV}`klz^izuSmx^fju z83txmni7|5c|MN9LDMt_R%d!oAi=;i2RXrdY&e-HUQi(MF7XoF zUmHUNVzlUu;(I)p=Me1!yVipUlfh7{!L+;3v9`OjwUAxe@935j%;AzA-!C z$Ze3hgTxnTI$d;B0M!TKfq#V8no{@7FYvw#G2a;NQ zN`Q5r3>I6GHl|!0{PnKA9DQ$US?yqn>JU)I4@?W@NDDS94rp{M%NRIi-Tua(Pv^Dc zcD3>O&|5jXx8)AE-RemN_scIKCGNSQhw?yyu>_k5<`~#yJ?J4v;K(AwXdEQ)ICu6-VZ`3Nh~=tiCej|Na02zFh(Jr-_J98tffyH3s?z0nTznO$&F7K9@= zs@Z}W%}cGwk>yWxbp0i6?ANokCY18V(1-*MI}?x2s{^SU`+}!cf4?&1jiZEsom%f4pmYz3e&ir~HW(YOwpRUu?xWp5LH z1gH{I;e-hDx-v7zL~6FHDn5pyvnL?-BE+bN<=JIu?k>(TvLJXxf;;DAj%)k4oD=or zl2L@^wWg}N`y%I)KZ=_0+){*)7G)3nUvJ_4ASudPRI{USA9CwFeS*TRS&t5hgzgml z>0WGE9g6J`ZK8+duF{Yros1wzYRB&+C0jZZt#efw{`J+DGSB>lK++v24FS^}eCgG0 zp9AHm$ic;j@ar6l-PjG%($FY>34!J94WnAk4cXU4C};jVVgOm_*{mBqzps=t@3&*Q z1u5^}lf$mpKHuM|PdgUV@dEO{+S`sVRZB(mciJi6$W~?j2s&!(4OoiEtBv&II~zsg zyKycpwR;DRLHWvbE%WtxmcNcSC zT}z87@^Hlz()%sT@_1L*vc;)mp;l?Kaf@QfCgZelX75>Z!W3HRH~Jf({WgE=UOB!C ziy@qhXU@?8Uzopb%;EsD{))i#f`s4(>bz9uZO|+XI-Gm0)UU;_M-#Xst2<0u)2f0j zDvoSx2#e%HfjUPlooxV8Q1}8b}&t!pvJtPC`Q9MWQMc61go9nxJ$N5F)X!v&+)RTNO-8KeYa8ML?l)I2sQ7`05^GezvK z2z9x%mMiIfElO;1JbE6#7S-}ZmO+8$hk#Geh0oPr#+D2E%T;3(%k&3Kzy4foO;VVX zYl>Ez>E*k^<+3Zx#irz~{WW43U2hEQH4Px_UE-c8mFmkUsq#dArR6U+_Fdvn2?99% zj3E|9IsgbxY@t{DxI7ZvqmO`!a60zy3_u3X%qRleH+%i!W+!h!XVEZ{*hWOOX;$JN z=on6^{LviXallfdBcZw(AB($&8Uz-1>HVS-Frq$N0=iXik8~uaZ-oToj5-Q!7JtSg zlfE(1@8R2^+c$QMv8CX``Bv?w#wOn^FYB6aJgEvF3iO`Te{I#ub!DCFv?8dlSyKnb zkxHeYN%3TM;$qQ+Qo^-hTg%w}>~7^_dl8O-o<n}rlt#WtDl-i7;cP3h5ZX)Ua< zVMF91{d7s@=MRMaNNNp<=0z3{@G9adEsooINe_u%m19=+r+O`|n_u36%~jAqO1v)E zl!_<~bKBAyiqPs}?>&+i1Lsn2K~qD^7sg2@6z6Qv#O_`1yy)Tsp76Dmcjn>m{aN%Z z--3MsN1lCN)X>@sQW&YXIUibh!xS8%tuEZ~&BbpGV96uQPA5kV2Us3kv|k*mL%B-> zIY3r(yS8h?bDdk=53s3%I@$lk^h_-O0n;08$=%k`2PbFe$~)w^tlT(VZ5yUiBdYGxj(;-b}KwU^2a1b|1d$TV8L9 zN8#{ln*A<5V!}g?t9xPMz*CLI@!}Igq(+GsrH7n%fwC^bH0j?xkpeC|+{JFgV&zB1 zGLLCp(tjf=(B&z>K4a}hKh9&YrH+BKhE<+g*>kwRXkR!AZJbPa^R{zUGN4Sf+mI(g zOB`DogDuFB`luZra+6**X0HtH z9f$KlJf!4b&(E~)xN$xUs$)^S-U!;gHKaFI@Hdm06%0gT>OYqohv#_RM{Ku79L8f# z4*TFs`o!d>UHoQ8=u}WvEJ<^hY}*b6lHaNIt#Bgd%H^wc{PN2lsc9&wQUezAjrOxn z&drz4-O{G2JN+mQk1a_ku87XVAD{+89fZ18K=z;o zwq9}r*M@!vm14t|VPi2u&wf>rpsy!)jU032?A38M(&dI>t~!})*7A?lV#;p@P9@cB z+vRNRZ6huh9!V{Mq0S4FHS(igPmEO(aSZ;Z(D`82L1LC@XADD}t*EJW+d(z=Y?D?C zO5$OXRtC;;?q38TzCJ^S)AQOLtCi5Wp^xgiWdz2gmxfYCl0}byLRuN16q<%OUl~~9 z*@S(xL|Lj}Qa;fEA$BU^I)G7KaUi7tDsf!^Lyw#>#z?kagxj)-Vya#Xx`!500Z-|9 zSWN#2@+6^sD5)nVzJqdMiBnSIMn3~bcJf_(LJtEB0ZMVo)mGoYPJj`##;?NaGmF2L zkKO|gydNLgs`Ob;R&Rlk6Y@yoE-P-*?-5Z7G4LIXw%tMS}~2u`yh4r=D*-1W2i|*L6X(&QX6h!idrLry{QQ+tlSl&kdRj9#XK^bs*lox z?Hw`9w4XB_xvrD?&j_j}RC_d18LNhf;JI^9?aLs_@AEAR){l&=L-oY9rV0PHJ_!q$ z*iS>Uel`Oh`1U*WDJausxT`73=Zb>1Tp04SZ=r(_8&a=*ML@ZLP>cRk5Ltlj14u_K zAeg@kk3uE%$!lPWmMymnyZ1fk=jzdc|JgRAUT@VD$r~g4ffXT{fTsez5G4m8S`NNWY#6WZ0#RnH*4hql! z0ueaJ3Ts&3!T#jo_sW+?7P9&u>JS_K|C~EAGO+yD?%fs*DX0IHT>VeBC$6FaaldDE zdbF;9C}-%kvT#F?fsLQon!YbYP~q@(yBGsTl2Dp_pQ==gD9();bF!+(wvqdI<~{?D zwiEI4eR0rr>$7h^V~Dr095j?crvKNeeTZj389(RG2K&iOrL(V#TGP1GbLY6JQ-ych zFSORNZ)QY)&1U!1Wu)f?c;>v#LuziWDWUV_33Si7SdN;FSW;I^%In%vS53~Y)Y{F8 z$c}Ae@(HR(8cXLUi>t)Ww6B&Ah!4D`Sb}39xnb{v#N{(Bog#9SrYa2DRZP9Q&X>cz zp$oYNI){YE{iR)Q(%_odm{9{vHW^csb8!OQeusT3+Ao2Y<1q(A6fgq1mM20K&^`#D znJ+e16|yn|S@twAO4!Y!Xc1uXun>RM7USZz?dUeC)pUja?06;Vw?F70pZHMF`O~Ge zu_? z_-`LfDKT(_aXsky{F0V+o)?#k3|u6MaVduxH$*-If2m6z&u$YQ=5MB0819WC&n-^IbxAZlySGc%+p#A{KMagcZn6z;`5iZh?!(Oz;_MCPnhu}Tjox=) z(|}+AXX78p**`SCAbQ3S#|=EYs#6_f#^~^{&Muz@4t?4?+r3Tco(zYlzdOF|y*@q9 zg=$)}w(Z>=y5Daf-v{@I{qSsIk%s=Q_JT8l6BcpIpH~tp7@bxVL9ki^RaF}RFC8Fx zV1vN@A3*F)*UsR`rv6|zqddIIT6L$!g9dJ!ES%?1j@G+I2xzhK)UA>Irt8N`ow`F3 z1?8+BkHzD*mN7fFQ5F^vkl>@RC6O7?f@U{k)G##JXveH}55u zYb740F+MwOcD%r|K|yjHfBe~j5OL1#LT`rk05{Vjznq`X7q=hqp1s)l7{B#5)hc8% zyY;r{W~eOh01*7+X5PR=QNN1oaLeczhOr8;cg6O{?iqvK*NIw$v5yOA33 zwp!(#z!v9%$l9Z_|Fi*uKc11VA@$|3tnIm6`EmdF{FtTAlnE)Tqt1+n`xt1&ovQT(E++@iHT9;2L4m9%GJB zm+B%=2+ld_XUYB9COofAh2~F~)*JJfJ1}+d%HE9~Q*(P|tD8x3_g-Netu5d#br%=i zR1wkjnZNAj&yAL$oF$UX&R<^Z&mq5MwJ1`!CCfJ1gB*gF(UEm*Kt#}1x;|YM=Cv># zSbS+4YBM@?l3fPwn#QY=$T%|+6>2||IrXxyliB5F5-J`W)0o>)iEG+G(OCvmlVh?6 z>8{*NzoWX0*T#q2nmXqi z`l)2VWEjBcy&bA0pNXMsIyxHJhY{+R91)$!2)SpNktL;?>hy2^r+ho6V5@EvnB#1< z4?`s;mb-ZwLHFbG*qjSrUp$6G0HIDjV0kWJVrdgl1Z-EXmlhYVE9^aMwO|Bc#qZqo zFUxoubXx4b-m^(-%krI@@f5rO`t2!J%W|`4TNJi@GVR6*t5`RTFp*uq?_CAS_pqG^Jf3deET?`0pt zhT{6z$k%8MtpVNxd@G2~o$q%sBP0HU&c)SLD_R&|p+B#tZJSY9M#{k(1m29XF3dil zlJAv5zz1=D&_l3juCsL#z!~F|Sw>0}wW6<3pEUtoprs^qE++tjBOJtNod+v90Wx>Y zA4S)EAhPdT*OhGl{UTaLYq8iuBe}=4QSOhvekt;X5PDr64_#*Y_%Af*ebm}AX4nX? z-}Y9B7*vo$9Rt0-_|2vql=IoQn&|D%svuga?G*JpRxF^x;yH&K#&z%?evi`Bjr2so zr<;~WgKb}-GkW__jQ(fjTnEH-5-yL8@T}x`Loq)Ox=ryFw}C|v!+Wb zVnl>7W2V_A0I^yDy`h8|Q`>@xt%4wR%JZv1sIWanW2s4+2@k%^Hr^t|M zG3r;RAcM1ts_5bx3#UoGYGNTJ(WL2tsxiGeG--E%yfh&3i$L??PDjZ4kkyvsp;XT# zPqj5jEE`B-<_yTEwT=uItOIQ!-XKVD6E8tV+0rSShTECUQd`_{N3#AfaPpf8 zz63`yK7JNLhR9LfEXkYcE(t?M_60`Z=3V=+F#N$EB^Vu!Oi#z5LO(~Gk2|Q){71(o z1>Y{2C!TV#WFU8Ec}S~7f}F`nVzWnUNPclQ%SFhUaqHYKn~-D>W~5k09z60~6@WOO0Ok)blny0d-M8CHN8LX+NGNQd|R?un>{|d)wN!K}P9ih)wLRg3odsv|v;1lFc(n`) zqYF!UUin@2e%*%Ys~Za(Xat-Af*vQOT{PTuk7|>c8j^PwBqv~oAW@H-NF!F|VyyTv zAUEJ7zoS@LwvQV5G-gV@EovBs3M?R<)h`)L+K6_!iPB^rwBr_0x4|T{&foiYl4kCYnXvymsti>)w%AIao()}Yg+0UwMD)I` z-PXt#i@pZRna!&Si+Us>1?A!8VR}5BdewS-IP~yseKX+z4qkTc*s%TM#DsThZ5!%# z8FgGLa^`xeH?^7;g%|o-I_+0I&)ANPu3Zr3-Kt&$4N%^7G9Ae3&XEC4kdS^g`4TIt z<+@su_TAa3ds921?ppG+@T%yA;fdju;kmwhICd&U=he4xD|M^p`rVstZUV-R;~s{* zj5_<4Wes2_L17ef_DyBLq)n#^?QPPTZm)wX%s9M*5CioEa(*r`7rb}pddJK@C%6-) zz7IGG4x``=lRgqm+su&+6eYWoP?BbDz==QAn(ApyzpSCSh7kRl-sVV1&x}}Y2Rvyr zu_)AH?129eK61>UuPbBDbeD7VLzJF~DZ+)C&cg|mC^~}3qu`&PSQ3ydDstwYPCrVa zftj38A{`vv_|ywn4f~f|8Pwx*h1$lCg5V-)-%K5f1T-)xQ4JWH_~bjOS^ayy)coH| z+JtiRf+o3)UBJVO^h}J?A|CMiM8~f~y?l0r<~<@E2GX&mGQOrkzhjo1MJh-zr&{N> zQE@Y8d}{A39l8;7x;MSQp=r*O4kw|2km(&#r2PIXk#F7t>ASk9h~Gc@7Tyog=m#)m z+VSJkumRU+koon5fcSTEC3PlpD6q@hnOk?yCgT9(9t47pR4X>rjLrvuY1Cdk zkd@}IrG965K>u4LFFK5~;zTZ?ba2FQpCr9-<13ISTCg%?rx+$9vHV^WJfqg*oOP^|Jp z48r?g(tq5EFVrN47504a5m@|kgpFt1$Obx-A9V^X(#$tVGF{KhdU{@7zJYyyFCvJu zFfvL1!UrB|*h<9MV_tLNUhbrOT_NSOiYUBkRLtZn46Wyu$#w%2hmJo_2tCW=W%A>}YMVp`pykNN#9!hxXZ5>6Y#PKj@x+ z6mS3Eql<#W-?V{(@%qSm^6#rr$N`d}uXt2X&sOgM=ReS;e=d>8MV%ijJ(~vv4JsN4 zC^nJ0*@W(U*^X5Qt#{keM>W5h!nE`xj3`By)u5vD9 z((3qv^UsIVKIa1W*}~Xe+5H^4-~m#)A2oWNXdgTxRZ>EMZF^^u{yl{7wKu|6Ru$LDSdn%li?iOO6(b=^wcOgHp>8oqc&zr;MF1G7+=IaRN>lqoLTej%i^Y766U*DXu*Qds^ z_U334KRxWJpPk&ng?}Hj#iWG(3i3t7e5?YTAbF{|Vm>AHnB2xAGxsk1tU8D@NU7|Q zH1Foo;QhURhZETys+LRGLTq0XCL4m$i5$1`(e~lMmB8YAPF~g!S5-t8zRxt0C z9M?ao$NuzQQiEP@ z&}gN;OomRTD6F}XXx^&^uEwtV3ap-mx)GVKGp0Mj7yE9Uba%b|;lN^G8u-qeO^cGU zR-fYMq>5PBDBg)E2!D?->HC?|;;M`v)Q0M@s#em7N3$d^Z^<|+s0b%e#yWhD^oCTaZJcYjj^9ExA?op);y z7M}a2TX~NE#!1RX{VU;#^UF*Gx*1g|Y6tcGs8&@QrI5RLWTZ;#erc(Mb3G&f<$9b~ zj@V5A#g*WP*O9Ek%~gvV>A<%=zziGX5Ol-IxRB9p?4x+*-bFy3oYP_?vK!nIKl$W- zMpjt&NeEbSE?`gxRv>j zM_w5s=lyj~cM+QfnUqU)Vm9nuu8`&_XLPR|a$NSWz(I9*M1v-ID&B1CJ$KMw$873WAN_Dv7{gZT}YHlb;u_~wagO|I^8UhU(~Z;5A*Z^@$?GzUeQIW zhI(;R5fxyO>Oz^#CS6rMJga>+jL7c9rmweG#P{Z1d;n^PcefSk-F`aVHj!=-+7UB7 z-~(>94Hs9(A_@WPNaxdgISZ1QsY+$>ehGbizLsDvbPL7GgTF zYUhTe{pVW|cr6brf;^(kI~YZ;z@N11h|{rw+cZF3 z5kvqCtY*IP-i>FW)hAYCzqNZuTR8K=reJpgXd$$~zT=vjCNW&tJxRtRXVBSGv)*T) zZ`ZSd@7p6!V27r065F4}_qdv1uyDD4mr?-6Hdo9qIhb@>wKJeOT+1p&3#BlqT-n4(zxA^#LB(Fl)Bz0lrj*f`-W8S>JLQE z9%7ZvPEKm)Z?G)IVYmT`ZCnCmY=BR+ABe9}`!7IW=%bLiNCit5z5$bH=|0NPzt(dk0$LFb$CtmF?7#^1Ktrfa&dx zS&_a4`COf)xWk(J)pn*IXG3+jexKL3-hz}2o6UfGU?P9>4=fH*u-jeo^#nXA7b136 z^p0@gsoww@jxdPJ2IGj+KwyJ|K%>bbWxILF|9)Z%YGwlg5kuSN09GO=Nu~hRia<@L z%8`#L=2w_dKTjI;KLE_zIJuy*-_ac&b-o?AEQ8slBijkLa+96#t^f3|)1_gO!}lhJ zIRzt}QQ*f29XqP@-_d*L@_0SagO#$3W-PtmiOi1%4U9)gO?%+?@f%VvHq{g;!50J-L(tNJ)(klkT|%LysT=HE0pi1fzSx6aKMN(iTdGq~$?+5z9B7 zJoA~Y%NAK%Wd?Cp z#;cR8rI6zm&9iUy{lf|kvpuO1q5KJ{qOYhB4E9(kZ=gjU}+QCUq~S6F@j_$AV&px zDwELSZA!;qFWWHFM!6a!`Y&d=0%S;p#L`v!5vSYEx#S|!{hF&p%5mCCZI{r8GbClM z067G}*`_XGXR7@SC2ZwxmMnPE>1OgKJD{3)s|Ap7kG=;8p3_wZJq@paOcSLdd8;(O4vh8yu#ppEhUGn=N_2bp{L`k*Wa45B|Mmzv1hDaYBp_E8!fjd$tOEoF#Gbqsd0(4nxxPG{ zY_yq5#JbVt&EQWL2Ws{faVd*@h^k?XJKf@J%_}gH@33`tq9_9`Hw?5kLy!y*JSM*w9SGr?7Td!AFEpG5-hN_xsQZhB*#!T2*H2!@Gj!lqnzx{!$JX8%x+-VBO0lpNezI0kedg3TBqS zo^qq+=js^SFE}~g2J(Nv*Bt)=Uo$hZ{rA&&t$+AhG_m)!uHR-IL8g5NfSXM$`Zyze z9d_O4@y#EsfK&{B9XN5}V)r&JMI{6%B2|^gAWRwn?;;^3Wmd42JQrG_y9;UmWAShs zo=;0fT!R&Lbqqd)I%JSbVO$VOWu=9Jl0eGh;=n=gt9JwMR;A^`xo)d+cdM?&q3W@9 zmv7Fd#S$KAlC^sq>~I%afiXZ~&;Nwyf!DsOXpA6e5)j5HD7*M~i~O`2TB5%L;{iEocaMKTp>aC{AED+~RIfg>TSUhzXN2*Z3i%$Fe?usBBeStayh*2-#o};`L+u@7Ho9<(`pXK>q zZEDXx#QczrBM){-Sx)6#Y0B1c$QjP($sufuav ztx^~d> zIgY&K;A(G0-(b%FElzlrb%z=~4Yc$1aSO}X{g#w`;fw1FCcT>G!PC7w6jK2@)N>)I z<-IGuS{6fXg*DoN-n-!0LxrQ|-1ykw(P$@|7yibKOEX&_stIfy=>2)xox~jN_pikX zMe4ib0)A@C{hqmu;x|&6yktq<`eu+_N))@L^(uU4Y(704hjlgJC3*8W<4fqfCc??; z_45e#qPu^#tW{%vp#-9m>2Z( zgaCGw{v*0l}&v`v-(FHx!)4ZcvQO~ITJ!yXP(tIt)W)SSqSudK_2)Vyy z#-5+6Lw_$mG7ihuonG73jW77&s5hlDaX1jXRF4IAv(vEu=JD!{@mXh$7237K3|fdxk8Zn_vW7QrRfYz>S(V^U$H_hV1F0IHFBC?4L3Xvq#}T((0*jy;En}C%tsj0DR2*&^H2brBf`~Ek~Fln^UU4>|aV- zRqYd2Bt09|`w9Ap=ATmCkiVy|+r8;h{Fs7-m}G9PteCoLZwlTwBr*PYw%Lw5fT5A9@jU$-FLu7+XH3+PBnHI=E^wscHY1*feNEN(B48FvA$P z4nv`46;dOm#4S;%6^Z{ekG~=z0bwDb?TU&1_OT51AD9QzTP-J}N>%iTZWR*ujwa^4 z0AcR8ID|7?P7-hkRw}M`W*Rg$vuFS+Mnbj{^DZ>zn$S;VAnq+PCy@j&hL@9NA}*O6 zk^K>(ebP-g4#38|OMNj29aW*FKTNd}plYlj6o}F^NS=LJLEVJv@ygPSKfovtv8_xj z_Yk2GWD2@dqM9J=IENqmzL^W>w7cNy(nc-kmSKQ}2@LI-)8yJTi0#j4>UxJ84f)FOx$Ov>) zpedgD6Tu3L)dNyXQQ^E2JP!ncq`4>{f$0}Rq839l^xjiqLs%)-k%m%L z-6xC$Z;;~Kbm6J{vQ4#)NUBk-49#a$!RgnQeQ_L>SVpRBCb?{w6QsjO$_`1bO!xGY zEsm>QPU|gfQjS&zkZBc6}D2J0Kq;RmXNLbSz~J5xsf>v zrbD%er8~xg!_@0Jk$0twQ%Le}gCMP#FJ;BPw8n{4=R!*V`taJV8cGNOr4n0+s5t=Z z92li$OR+IWAn#mQc=+ftq_Ta2Dq%H#hM;@`k-3$yiBB?~0C7g^3c;itzG0FkhnDoA zl(-2IzAX|CMv_iU)C zLu@KRgM7d!Wh=jAw*(bLtfts(M2T*GpQ$-)+K_@a=l66tMvz?-?My2lMl`SeQR}4} zfmf%`L$gowKo+k~F1cQ>N>I#30L9SIgmHo-B;tYJOi=(8I?Ey446+u%ruE4ZN!?`EA#G5^=vn8i)kmmz>R?_ z@lYgdpYNAmU{SEh9f8x<4fBv*!?e&$o-hudy?>=)bkqh((&$m+V+d1{Pw%2ZF4VMk z!D7UNG*-pCdVoHjSN`kkQCxJbNwNM0^LKd;fG*yh-%&*kL;5q&TiVKuQJLTqsI0}% zPJ@FZ2wyzKNH1>y)qn_e}=#`zYsm?^(OoX1LtsOuCae7dVma z_#=kH(`-is^?+fYD+e!S853DofT#felrDJF+-vg$T2kfW^v}+>7I+}*t4OTi;5sd= zas%cY*Zk;x?R$0s(*6!`x0$|y3cb9!iUr!Q?Lv)!CiYuHkbl#7MBJ__2io2%-}l@- z3I9O=C;n6541IJEVC;(293n?SXg0EB*8-xd%FHVK|B9S&i;}9JSO)s-gI=9l{jxFUG(?rq&!~QVD1B*J5~S5{_f){k~MR zOtx-a&3s$Hui%HCHJ5KTWc+Eb zAeeehFk*RsIsz=o#HxBumm^L@N6?N(aI>{c=={VzfjDMc4_&Y-I z!;cb9BxE!X44>sDz7q4>;5XqL5d}UDJlcG4DV-YRhQcQ27O|mNm_(c75ejGb5v{xN5#J` zwWz)>p-Ne!FAf{GGblgX|I#GSJs)>{)*AAWd&LL4hIZrwtryQv^&dX}YSpP38N%!m5I0;CGI7U-uOpfqWKxI;tVw9j!CI=kmcAhcVL3Y%ty!v7ZWq zj(;IU@qQ|O`J|ed`Vx`>ajK$5g?-kl92jc=`u+S_%t4I^_Do?!aq*?V@D4Lkt`7OY zKJ&xk`!yxx#mVyTRiq9xLyPCZ{fhH9$#Z_4^k|?+^+`p_vJM}RS}%TEZyD?<()TVP z(9fLoULFEsC0|ZZk=S?lQHEum^gix4@0r}bW2eqj>wPUp+sx&g{8h$FrkljCkGptd zR*|<;lJ!)RE1+srMhh0|rUQ;f}qx2i1)G@


O+C{t!nK58|+A;zQ4QJbq8g)sm29wlet`s98z{kDQD5im;UUgYJ`4mG{glh%f_|H z{tlRAyUmAzcrNJp$6Pw_@Vpw2!^dj4HMpvqtKe;U&C%~#Lq_9Gp`}MTBBP#p%UBJM zFnE(4Ij{Yg?mh1g`kSCS)I#Q0gQbzn>oBwh~)lPUEGF-52l^Vq6HpwVXd;**T0 z>V&w_6KjbY4mj7@dnS5+Y;)FH>2AU08y=YCWxcybkv^X)%;U@GjmyW@_xyIq2)r=? z{!*di^MKjDW{5wV^)n4!`x7;%D%a6{?%W!BjDmXB*5RJ(Sysai!80IZ*ZwgQWsMNG zSjYIW>O-RcdxZUaiZHFJy6>Bf!Q1#=NO8FXP*VVaIXY5LO)kff$2M$2@5l#y%7?V^vDnCC!KZ#2X7MwdmBLL1v=uU`x&3h z-g){n6ti1J7YbXcWYxoYv!0z$YMCp{Z6BxMIFgQD092BHJ2QH3I6!CDadD)>ZXv_#+AlJ)BRTeCu|7_WanfyheVk`eXW0h;r+AAnhjM?UH8cWRc zkTxPUBU(;L12uRx^EfeA9;ch4^(IVLC^X)jw9170WnngZ=~KnIK0M;WJs7vh(R z*q-!!?R3uydHms&1Db7$09UnrntRlk@hw zc9zw~jK5@HueBziCVCba+T^3+VJ#0y_XTk*;36D>G&k(fv^sX%>lG7b7-`5!*-Ui0 zReg=Vk$=I;eIuMC=9gmb0o7)3E|HYBuX+-xq* z)s|J|&uwOudTB{r57leUF^vvTb{#cpCkw4)8$C?lit zEfxh7|AQz_vr>?Y$lB3rC-|L zWGE(b1QY^9P);q03jyonZV0(3H|S)QKK%pQiuZwuw_vTOsn?qIxh|@p(p+$_uFxOB{Q$qQJy}G36Y*gdJ)luB5#MX!@`dLWFjws7ob8!|$%SduQIpbJ?g7gHe9n+Nca5tHB^ z31aQJsl#ffRg}z}O){ol4kE4@nL))B88C}XtQS44h|)ylAOu$@DOH_o9bBz)aHc9G zJ2q`gW@%|5hgsSD6u3a}O8yU8@&d_ny$ib1eX3Y<1Eqa*8FK$t>aUl`LB2otrTWI{ zCokYrgTkIFGkuXjI;0af>ic;P_Miw2XclK49?hqUu1>4q^^7QG{zvdUE!h%X0~NXu ztI{z7Extz`MIaL(HK=)b5S*bYmqWsR9Oio@$|4X(fmsTjP0z*+Y+MHR+lfc5a)7pA zKkgp(>YKvwH!7Nz8=@*!(>PMiKU}mN! zEz@Ahf#vF}U~_1XRRAX@wX_qatJg)&*leR=^aDpfa-Ih5FG|r^o&K334GGgb4w*Uo zWT(K9MB7u|k|%MqGuP`RjSsqj`?OKBhxi&+vIccKgp}4Uzh86clnmt5HRVkTUU>d?G)SymTBPK;uuKm5~CR?4hgo9oUU zJdY}lj9_}PjsggZd`A4*CEVv3y|N%YAvJeaT*Y#0FLtf*0p}jPH|NA_&Wt+Wo$#g| z(BklQRdCePPAcN1O5{E~93!-0N^*qJkVTNrs5L81#!b5op&D1MSaJ`%k$|?0&gDdU zr@#gWdwA&p=|nI3GIa2gv1K`5`wo^$xvL>nABCZE3N<@Ci>wT1&mr z@WKP>K9$jKVVk0XY&MOF=(!_jaqYMeW9K3{29Fs5$6Kq5e3t`%KKvk2&JFl@wlVgV zI9DN_oc6zOzVN@sf>XDG3Tlm<&E;~4Glg1P1GeGSxn_BNygpH!ZCj(Qy-|cA`2~JH z?d{C2|4E-h9^sA#UxHU-m^{}DQRXsMZTQ0S7Lgdf@aucHL64g`Is~(L{Yh6LuPoA9 zOgs<+1O)h91oHdXE4^79{8hQmF@fhIzsh)@*Igaju5=WIxHbsgyFD_V_}oD1jIia; zjk-!2x9RD;bCG*qxBC1}E!x`0jr=?GcL2)mOz0~r4D5m^{B_na4C>Vs-USmaVi<=t ze&aJ3@uH8WF%~fAiJ8GzB>HglC-l~Y6GrJqLF$WP^1X=uqv;O$wqBR7Rm!^Y#;3=V z(PRe`KE~p@t>iXKH%{^Anvd{L9ML9ksOQBf09K{Ehu4}sz5Fl09zB=Le;ofz|8e{? zv;5CZ``uW&_FHTZ+r2{ey1;s>B&1Rko5wYjf8fE3?zg42P#RKRD(0oMC9y}ppR@O2 zD5W}E0>L;4G~P84biN%?Qza9M z+(a3I)a}&i4!WiatD+vK?7{)=W8;VaCJ$B6G@LOC-FbaR-WvDu{ldV{3NA9qC5?%a zm{vXV$QBB_H1meG08A%nP*6@_c1(+8jj3En07Z+IG$14mbx1@qRXElL+VQsO2AG4~ zq+NT*kXwW-1Kw}O%|L(B<2d^h^m)j6V*`DkFw12*!_-};vcqtEt&C5MdsI$n98qmV z(8#IU4dV|||KP`53OA_$#tN6w%p?$PSY?k`8r64ocYpk3&zY)>6oWC@-4e&2b5KV} zpo_w=h_`m09h#ZPtZI|8f3L}`_U`;(vlUC$L2`zc1&2-26!Mx?h5l%vWs2{_j~k4R zRx4RO)=H1@*%>ZQH|r|k?i-|K*o8_Hqf!^Ap2~O$TVMV-Sd;{w-zi9+EW@`py52tr zZuaK355nZUf9{L}L=igunoDc?iD(WLjG1)QB`S?-MYtG}lFkt%D$VpU1*?Ltm8s`q zB9-NU)6tOleQe(ZX%^xTLSLp?>ZOHJOlqKM;t7}nePo@AI!FLOSv<|f4PlM_IRVS5*9VU2op0C#`dk{}z(@-`w69_o}AU*Po% zD0F^$E$q#fwVPS|l~l2E1G(h(F3H5&y{;^2bR&Q=xJOD?{Z!suxbb(}%?_D7>3i+A zfz>`a6LqFw5NN}JPRu4q#LwUQ>_PB&wW5{4Eal@y#~fLoDT%R&FgIba0{);i6H zUWDjp26C6l+s?@g_MBRxpArTPQ9=!d>VslHp`r{71X2(v4-Ijd>GN#lCgwPZN;M(a zr`2yjNcRukH|-e~Q38DrpQ~Mm*o#HhXlX)ZmaTrD4o3Xc)*qbxDX1^1*P(hn?6F^; z-c?0jAArRa+r?yp#GLdtHCR+|HCC0E-K~c0+djdBcEHij1Q4;IR3)RSotq?gtYLj) z&kE$>N7z-L0#!T0BJ%0mkDae|YYBY(8{42kQb2)0eI^dY6k6@ane3-N;03$1KzZ}+ zrdJocBX&zHP1K>2d@6Zik%X`TfBX&k1I13v5rO_ zgwtcJ5808DeOy6ulN?KSx__WSRuo{U8fgf&cf}s~Zn2q$J)~6u3FOfVV|D?n$Hn1P zAUeI8S$$XE;IH0DUyQv`chIyZnVvCkhvsYHdV0_8zw?G$5Zf&(2 z|1=jol#jSobO7ee7xV5lCLZ@s_mnAMje@Xi)q+OS$E{fiW}9-H3f%&Sq&Xh_+JV|V zk<=|3S$Bi;N_WL3Cn-)v3+uIKL5o0_QDEnuThG3z?V`i#)8<>85Mc7;>f4@Luy z&)m`_^lBNPmon}w@gvZ%)}429Blch~oz@0nvsgJwX@je5s_mR>{c38;5Zq{A*?^%>#_*3 zGN8PZMEF&d4HTFwznmST z$A^xvM=~dcmf%zsxU%es( zOg^T##&U)u31nahntz;fb%9Ko2H~;4$uXNvWyUlc@k-6XiG0} z=(CG1#b#A}xOD8r1q;)`{Tkb15aN=YFf0b7iL^c?O=(pD*p;cSY(5q(vg|4bwNx&M z#e1$>l1;^*v(fmxA$<4bcr*9xdh7n|T*2SCiMk&aN0bbGB1Vt`w4;~$p8 zfv$(VU9`p>Iu%`kt63>F%InQP&f>S8tgE6hVreIP1fo)@`-2bD#BuRtdGY#rQv*2c zvP=b#;Ly#XHWlA7si7Y(!q)wEZch(?&*13^t>_=QfZeOsp+cd8#Y&j3D`Uih(hNeb zt=z`gK@1ZY;{pP>-d_&eHZWYUAE$@tKbH$CYUH&`SI4>hiYU1Z;RsfqbF{*e_ZqKG zv_p?Mp5TRtlVa=o7#MX_tK?x6eAquSk@I0q@c^_$5c<46NEJKS3W?F7+ezCMIL@<- z!E{xp5dGW8Y!2YuwlgAVJDu#yHk0k$N;g2P315xd<(ii5*ATbrIxj5z?}{2u&&;3d zJ{no^PgN?$5e3a&vm*OX{E=K(A!wA9fqw;jmG)qCNlnSKwEWZJLGg)2?EkX4I(!)L z>RlW)hycNvg->^&Lx7nz7u#X;9dXkq6=^8eOyGQq(Ii=YnG{$0xzm+#6st$tg@ImS zFO`&vmMLN{KpEp7MZFDYJ15p*m7!*KDlguwb$%L@raY*i-7|ZugR(tI$7Fp2YRI3V zl+e9_z%R;p1vpr^m(RnD10w^T_1L=`%N+X79U5ecInc)LAeDzpq|Iy;O73HH9(uKu z;A9yoHeAC=pCk7wB|PDDRnKy?m^qa|;kfK~-+&7+aXR0G;uf?M@=#0Lrqob~x>kMK zt#b4eU7TX%0)b&ZO_SfYpGwg$8fHveQ#=SRo+;z?C#enwON< z$LmUv@9WyyPhVQ?Oj2Rl49jQ~Hcg4@*D8gHLYlM9Oc?sGI?=FhNtK>k$h1m{rBcqS zi_7d@zS{mga`ivr@LQXR7S#nzb3i-54X~YumqK0SjVD7vuWVBJMFLa;#OXT|`bGQ9 z+<))HeD!<+^-(KwhUnI%G!8fZ5$op-(jcKObgZBAiuQ(!8=c?+QHNaX370joStKF5 zlZUbONWbOLSl;WxtzWRVdaKKvzoAK?;BvD;ZJVActaZ}wiW;ib<8sAvYL7MN9!p?i z@YsA#?esz8SQY$fEUFFsGRJBz3v?h{6?BLoH_`;xLh#`g&bk_%FbJC!Wlc?XsBlfkO^`0Q=k)x@o0_hFEU*26`?xTp$Gn=gn>(b~@9pxo<>pt_54j;liyfHR2pP z#xESy9E0C6%Ys9u4a;3u#`l+wR$XtqQIL#426UjmrRbrVwcSzMVgcc%K*tH{Bxtt- zuXek&*2y_Y!*uPP{i~@Xd+jegpj(MuT-{qj$zHDHQRc5MI^{^>C;5%C?=PHHov&XxlmfC4HfjI2zsX3Vx!Trh7c?FV^&WXkje(DxP(=)3Gsr)dJ%_z73b8@W#KLDop?XoqB$06%Ryn}CYs)wYGQcOlWFW*J*r zq3;Bf@qfuocv%|SL1B@=T;Hw%{we?9z75es7>U4cA~F{IftDE8a`0S=T37`WggTwq z5iV!-c~fULvmTCLUN&dI&|x|s#+4nFR`iS{&9npsbRXSG;dfT@v&+S8O3dPfS;~e0 zj1r%EJsY_dnP8WjSTg5E?Y1;}5_K9hrTd!DlSX(O23O2WWZ7BgI0Rhxegu>@g)C(T z*5!G3&`GE34w50d)v_oCua^;)3CIjLqS9oivK1Q1?M%G zzWlCg*E=+NZ}#tW^5RA##V4L#^rBTrsL50#y=je0(9Ig3gV)RY4%uA}|MjoN@&5~8 z|9@CoHn#sUmNpr4(&>=x;ih)a_KYeL5b%zb)708sxw&*Z%06)kO=bWIIYI?u0`GR! zp1ngbJ}F6x4sG57;QOzp$ftw|KOe-;_vYiE`|p%VQi)`8|Lvo75}6Ri8j2!Tu@ISQ zdEq~Xcg5t{JBCm6=IUs6g65XolXj+~O|{q8@w>i*dP@iOR^lYd=Fa1$uYRW?#`~5o zR#91kjQM5b%~M;VsLkgHFCwY~qT=wSP58UuD?!A*1CHsE2ntq%A3`2UReR8 z38RbsGEKYM2aYB#b!%BB&HK1+=Px+l-fFY)Aq3>Iy&vKJtN14 zI3FvROHj5LU-2fygKKkEEQ^5Q!XSBn!YdAt=fqOjK;Y(;-L)K)5p0wPP1tg3TQXn zisa)S+cP2Zd2>2a>YN>}=E+bdT~Ld;6uDmOWkk4(6M4XaZTu{f&qj4Kgm65;it|CB z!{or7g|uV7Z2>U!&R3_ZY=n;%W{M@6)D*#PsHqaB;W2iTAkh2VFREb-4Gai~8W3Tq z`zFARkqd;81#s|H0Q+VjFkHa5So>*fK`O^3#=y=tP-7&p!>JK0JU!)0agcE;)iuzQSF#h#N!mt0mPNKg^leg5a|Q zHBiauTiVT^^dA=ti^f!G_z!ErbhSPJ{A$^tP$_UU{Uvnj%-?N{mj}MSLyUQ2K54rbS=c~i z6_`KL@-0GGynP>V)1p4XpM~$`*QqBYfIu@3UAu0f4^J!A?e7aGLji-_>!>S88 zE1-w}%@-nHCaOrFN^PfS5=l?mE9?juD3}=MrLO>}Me?r?6lB`f8vANfJZBOCFD?sG z$j}uYu2oswBGXO9ELx*pXk_}7E|N}1anQ`)W0FjYtb9r>CkeFSGk&kKGwWUIr+2+e z+WDbLs?i2kRZbt4Z(4_cf`-J3h+^6ChsfgssKt6I+q`>KDF6mVoee0o=O2G&I5oHwWoQj-rt9PS(x=l)F-yrp-9omorAl-Lf# zs6C^z|LVn!%eGm0(fN4<0Y&4tZwTn5d^1OAngFePs3vy2;S(Ef>7Ak5YmR&vhS0WA zVKKg3MN~Y|PC=0`R9XdZD=ClQoC7xqfdu-)ZxK0p6n!^9Exk}4rbg+`ncS)u>G>SK zawAh75x)i1F385+dD?(0s35!jj}9heoX%{n@TM!Ht;h~(#MHC}-w5u(^+(tT)j&Zb z^XfaURw~Y*&!2S{z`%~r`fFZat z@5x2JHa<(`nLq9|DSJlD^QNfB|ek8cq&wEyrnPT^UmtMcV2X9KYb; zGBz(D)pdgy6;a6BM4NdPrh3G|m5!Q$Dscyh$SWX+liVQ)ix2fGgyX zG@ceP>q+h50-(x7%+-C8igrC~ubU1WG~i1iCU6t}fjlMnF#C;=1U5EVJ7rfTMVS>g zmq@e=cw0R+Po)Tjq|Z(kmYcaa*{An_)=>mQ)R)V*b6`B8`cMKwtp^?Oy&~YZwk%AD zJHGBmTLOR8`o^DqYIHj(a( z_*@8L^49QfV~kkb>u&!P6;up0F`cLvbtHAH4=h!eU-4hS?0 zRa`Pwe9A?ppI1>Lo?aTEsnZC{cOBafhN(+Wk}yCk#>C2}6mUq%%CZ#Io-F!-R&nmv z3}X-EZai*Jb1RHvzcNZ=iX0X z;UKzGj5i2l8ZHYs3sNwa?(GwgMP(1k!{(6;P?3j5M}xn?SAINEP8yW083Zf`|&yl*^76A_S~J^i0MUDJIsW)R_X(B8F9tV6qXassZF5 z@#mEDi(j$gHVBv=O@rdKr5FVjuYahZwCaQUqm^mN-%Ri%T$IRCl0kb^i-i-J?Jn_@NM~cq0r2)xv?B_-}BGy5h+l}+Snbn<#y6FV}=$H-g{dK3OP^7KhFM)BbCT8+bEpA;^;b& zCakYt0TOhqr}qeS#IxqvvdCfF8sTTovy-vY)7w=rhpQtedP0FR23^E}HKZbj`+7B{ zKdn-2X#fj1a0@aWKWbPzGd52UqKFn93tAb$A^$HN;`*hYa9o(pO+3JX-CZ@aRrkkO z2x(Qd{ZuHRuz+0O0DcdcQaj~mlmijMzOr3z%jb8}2*SQ`O zD(yY7|JO@~DI_igNg$>|JFm6pT>8k`ZJCq{(hbNT0h)Hi5{F^{SNev8-1!Vd5{`@E zgq56$GWJLBQt7fK-t{~9n*pzwEu(HSCAB^#Au@e6a%!)$R4AZWV*lvRPD0IJfl=X*3B;AD2a&G=tIhbjX^GmMtsVEnen5 zYKQNEKs`5uVKdD?#FaF=YB4`{=AS#ickb$5yfhKYIw$Q8UngnExQQhW&?oeqGo~kk z_gHItw@-Z7qcKbGu6Fg%*kM%-4ac3S7)`ysD4ZV2s4m$bziB3%Z}>-Gor@}4m{qQe z>|7z&P|JURi+CbcY2k9|Gf?KkKFK)mDjd!B88?u7y653)EjRQ%{g$Dzh-TAQ6TtiT z*Wp%wz+3Ip)kG&1CBEAYE44$RbX#A*W_Uu>Q9jL!RK2Ngboeox9lkM#2wa3SUqcc3 zP{-K*gwgzK#eV8Ix?KZ{+kwPvbG2&%(~XU{4akcdS}(?)GRk~IJY`%$!$3fCw6e!> zmq{Y4`&OO zc32xuEPtlO5NS8|(i7&POip9QHsOy(@5oE&D<3)v!IoAe1VGRoO>t~A^1nlXeN5$5|=bQq_GpsQHR4*}pKNr*+-7?m{Q&cvDt zB9=SrC0_;F#TzLA2jt}UH%oF=0E*}!1yAde8AG_Y4nvI=TMpi_7T>4J&x2kJE8p6P zd-O%|Ac|TKe;^X-XF_*$$P%-hFXu3VDU+Park~Xj*Aw`X1eJ+Xt~v9I=+;sa7Y<{a zo1Z3vO-hj(BjPs3eO=oTCu@gQX&b0DSzzYt8*5;e|6XmA43kh{7}gpMjKl$%z9vZC z(wJ-%KS8LKGb%>@7wk(N-!8_Tt0szr2{?FBodtl4*~-5wQBui^iLaU9NTIs*cL>j% z1KAUsUV79KZt>Ta4;o6(S_gaK_ELXeng?)-n55};W@-EFa)_)hedvHc_!OQhMPyg( zpQMx#wq6P(w3nlPLRwjfz9aI$c8C`%nTwx*G!hk|)7AmGaj@^^V^ahbte|oys}OXQ zlmzQc_dGwH#}Gux#Opt;K(9B4NlTm3@LQdAFzz0W)`S%#>up@V8VW<7hX<8CyKHWr zJOXi&?T&*Sd2k^GaUL2v32X)g>mhUj8Cmjtg5e*??q#U_I}$tgDW`;*p?x}hDbhV^ zsF^%!NgJEaeJ}!EQ%&pyz6N0chk1@r0HU`d_+pcs&fcmvf(2MGXBM269)V(d0vW5d zkt{gaqHcX!l4}OjE$?8WCUkTkPGzKO;#@-&mArO`K1BFp&p;9f%_w2F6%QV6E@=0a| z^oB+{!A=F#ZoNBM=y7*dVkj>Dq4Ys;!3UjT9>k9CzJxR8ceWnwtWY7eRbZz*IxeR6 zS(uONZK4LA!>Cq4{AbbavasszC{CiPYM|Rpxnn(@1T@^A>=tr+W=7!YFdZ7)D;AoR zAh^3bA71z{MO99pMQon-vS1|HyWa2er;)@|8=^le`ukbL0PO>aI?*=K048Z)@f?^v zKQ3MpqLVi%9pCv>8bF_aTtLu*3bakZR)~rMgSiGHbjLQfrv3?{jM=ACo{B(AHqelr z=!9ZCO5K;)6fWuBu_xa;`d3WbOGxi35h019Fy7M(^nGD0c;bbE8C8-SzE+)%i|XQP+ub?{e@LyKJjg z7Dg9@3eHs_M0^Uuwf8)!LEuUqMd|vV*W193lB^i#UBxlP#xOOdC43O?x{5q{TiUYE z24Moi=$bhhBfPInh4Y*`6r5fTISXKZwtJvf$&!f60&8XJAF543=N?-YJF947qo&9S)9#6kyR^>qcN3VPEBb=ra z&T>mmCb!@P9CyV3qGUiI{YVZfkKj7-Hi44N$-<9QsltxRfr!&BMrSU{|dYwjv^ntxyH6u1>|75CH3tlU0%oi0Y0kR zdO4&z9mN-XY^rqNhG~eG7>nY*Njl1r3NqQ#y^A@q3x8wfi zYluCdixMUyyaB<)r6xz&#G!PPL^LL~t?ht|Ai|CC^uB^46~ggTf0Y6KdycOl6XGOG zl8;J*H1_0d-VMLae@)X;u;xji{r$GV+1dAhUrclT`0FQ#mx)3^q7dt$`3@;FR561( zszF3Ng&&YjM*0IHeK{iQV0#lj7Pe+FDPigT6R=O%$9rtPU)Tf6oA&qsXVJNp?2 z=Sv+O7VmbA87`XC98*j}xi)`h6o+xI^>_Hb;U$cWUOsFP7&6{`Z-UJxpdpW%pX87pvWJGjkAt);{=YDtB9rh=e67j)$~0$y<5ZNUbX5} z-KAySwqd#!pXul4;bXs)UbTMwv8i(Jvt#?nGDVdk&Gtby2i0;jLN@W(l5S)A((qCH zK-GS9|Gbz+gozJTmI=G?H!)d=lO#pbzKD+^hcOm-UXhlRGQzM7u za7DZvJcCaYuV8kCvNtw~2NUe<)12tv$7FZ47w9zgYt=TJO|$s!+N#ebxW$*rxQqwD#CUW zQ7^v_;x?K60#PkU1;J)llCD=KnzAQ;a_@LGU?8i zu*(P<)PgM)-OLtG@k>2mded~A>}-kMZdQ>7?T;Vn=foD`n_oic%=YaNtlzKa!4vj0 z6%tbT#Ah&~jm+nXMLHMjG)l`>fu}zTmxB=;v3GM$_uFLZJkE|o&6i^OiZV*A7Pnh@z7~T^FW5D_vs&-244)O>qGu@z^`*V=l1gcQ%tv3rb7H1c?2B&?k zW13%UXUB^23ku-HG5WloR#SK&$mDn)<16dJ5wd_U1)ghTDs>u;4+reL&F&2v?8uCn>ApA**P0T zor%F6r5=|(t<0P-_}yG~nhnO>5SwG+oGRK5L7kA56VMWEIIfhx z%Zk*L$EmAr>?DcnGC5p?eTHmYc+-~g!6(S1j@u3h?}DBxuSfGVHs5=A)2pN-Q2?e$PQo-$DD^_ z-}Z}>lKRe`=F9Tx?6 zb2CoVJ-TF-CY9xqf-+tZ;^+6@SQ+W)+{UDd820#o$tOiVEs)**+1O>3R1_8hSFLW; zl_kVf(uBtjDceHMrN-yX%>5Al^oY0MWt<;aeyS4^Mqm-q6=4CPOK*+}Az11;;`BiB z6i^Nhi|`8}y1?~wS{>^fjz~!CH-{T1e^BfivvYJT<&f(8Zp{j6Cc3B)&8_&oKhmQjTw|6xyAG zS1Oe>!+TV5?W{J^24JCXJwOpWN?$<^Cf_6+CZc)3G@ch=gU90GJ76N6DPLETvItYRspQOldljQKFT;;!;RI6Mmsq?u(Dfo~w4xFLxFO*ebR)kQ;64&NI^jv7>q=`?9SQA#nMWuN;T z7!zCuO0EqnF*NM1Z1#jj@y~F{!8VD{*vqBGClS%WTpn{7&-W9=G8!#OsRnPa;=fcz zDFFP{RRBw7h7l>qC~?yF{y8A%E=zc>*VT<>iiuSmURqK{*v<6|&9XrR2Q>w(SgVxc zAw{WV`@C<#y#s1dqLaffyKR9iuaXSxyESFDcY@9jOud75NI$444r{|ElmI13J!bMzz|P)sCyYf@NkB^D(@c}EoN_XOMiZ5 z1ybN3(n|yKr9REDGH{z?z)iwnqiGwk3#+MS)CWW}#bl5tX0rTla4dX)EUr#2h+=no z8#Gdq#^N_n_Mpjz0}56qgLX=)jL1FxT>U3CMmCfVp1;bwI{`p3v7-N~l2ZzO;qOon z0P*ePURX|bmS))vOrwQyUFpm1%IeU;m(*0k)8$igGTZ<`g2oD9H0Kp0sI%y5Cy)_Z8VR?`ZxC|(A#JXW?^XCfu7V9+p>xw`vYH4rMPdufCC%rir zHmA=W)Tk5Afnfy4*xaj$ZwXX~10JE!9FTB)$(bJ~J5PaDW+gJEgf+rFwt_}SUcJJy za%gw_@#P|?1B&eT+>rO&rn;;Zg6l)Kxz!La@|%xo5sX#G=@SCx1%1T8NzDzs*|RKP zcl(npNQSVy#c@12(H!oN6-Oi;?Ztx+A8sgSZezeD%1?n@Tu(Nurk^Nik64ddt^E1o zQozKd`I*ar((;JwC^d@^XuvQtbc$* z$Ee4Q>n(VI8M~;^s@|ec;DQX?T?e2XMWSL`ZM}1%p*J6e!jH|pZ;MGOS@~nzh(*SX zpTKo|ThRDK7EWn;#Gxn%WA9zTmJgY$BS7MBiO5ncm`QlEJnfl81yPDLnWiplpj79C{p2+<(*Jugq(&bE=JEsJVD*nH`r< zD*-H@e|_x#ZZ4jl|A$UwW%&=C$o}u{|8qgni&@&Zm^u;Ai`f{un2MMh+nbm| z@$o@9yEvH|+Cq73{417q{(p7i9IZ<7G^qd(343$sxQZM5x{Xru8(ShUNk7sgN;yDL z_e`z)|6bxZnB3B$BhHRgeqPpUuUqv_sPX$uynU92%I|)c9Z7{XsonW_=$KmS3hK8Y zQOA!rRYa(!hpV70U+lMiWVl#sk{%-ZvR`_=HN){y$%h1wXaG*Tw> zEuLC{ui99iNi*Y+Q35B+)$j|J2Z4g=T(inC|8-dz-A{qGj^}7>zJdSyeH+?)z zfYY&HnJ&pSYPNMvOOrf_MHYY~ZptMF2MxBq<^pwkHE8z1Ry@+14trf=n`tL+D-Qjt zM7Fih(`;9FoovDtn2dhG(20u^B+kq3V-0pWYyHSbr!Sn!lq|JKxwW>zot9vJ&)Q=4 zl%myR%H)O9nZ0eOyC*ilYP9QM!PA@*g`IJ|h9%DX(n=c>XuZ~$g}&8jsm<*tB52FP zE#2{#fbn}o`?X7ri!n2W{3UiD;ot$JLWWupJZ~~Yax?#27_;VdE|gf{(a&B=)O;F^ z-6@ZIq0Efq&#L$2!xk4di?acu2Ap;Y*HzFFu0{sCxU}x$V@ccY?O&RUWQv8ZITOUn z&n^j8HMhD$qU1|2!XhcWfI21FiYU{?>#{o}7EI~=nmsQpcH2XEHaG|=d@z`%8(Zll zO)ZpnDf6{4ESX7j4`MTLsj=odEbq{@?U$TPy!{j}aqDxd9yM2|6`tU8k(y_mTI#~i6g%XXSW-jzm5{lwgMSGu!HAI$+6 zSD%s8OJ{LYdrD~Jt-qqdzCTZpD}91Y)1CnNv{Su> z=QB(tZG;xWZ2n;Bx3G)q00va%i}ncQ)(bUvpu*(kXj1bSb)i#tCy9X$$+{4~$AOda zKzU7CGmsgvmxO5>qdS@vcAvxsF!?|$(<`9SLl2l*`>oH5e+OTqO((HnTHk9yt6nJu zrJ+3j_7n+)Cj)s;S#2zw#=uO03Er8^h7r8{bblgrNC~Q+8s*e%k9eOqglx`5a$c3L ztq`O1L5e!DIQX^ryed}EsEW47y-k95`cX??8nru%_@08h(G(l0^ku{NVb#9o!)vr#azc+jV9jX8-C^2uo z$vA6jE$&~{k#&{hXAs-sWeaYC10=z;&JA^DI;4IhJv|*dV6`K=KF64XoQ1R)Zdl6m zb}>-leY63kJhDft1pH8pKqE&9sN39|HhI_>EdUoAOmuK{{sUORR3Mpw)A$w>mOS>i zf@*<2x3U36|6<-deawtEZyu)SNT&7v?3{r%Cd4qc&)}JMvGV`zDX3YL zifoL`;JMJW1jIv)8-rVu-{q_I=iSRj0iu9<^Qz*njt|#yjpRu0&Z-FGr%>U#vt1II zSPQ%;U3#TnU{X?=Zh{FBO#uXsBOwfrq}S5l5>_m4LWp?ISrb}hjBmJEa{+rVHI)m3 z-;9md2HOB)CQGq6YqX6%g%p&oi&k~x41qkYUAZR5yaN%Zs<(Q%=M`~me@BWb!h~Ow zFadHJC1xzoTBc@Mlyf?g+gf0g2V}V|J3O94fGxC}<1-EMAGgD677so6a2ViDQQKdnA+Wk4cEv8Cg*TF;Y*=}qQk}#+rx8B|D z(Td9pEnaW9sK_=D&2?ihuF~Z`N={FnLR{u{p-SgL%J2X@z-heH#O*LVo1MN^PCeKr zwx6PsdNGrzWMHQVI9%E3MtSAn6k}ClRQrk7wSfvOcYMsItmq+dayMT*q{JFiL89+d zV%j4OC7%93bptZm++U&KYriG5Xx%Q;<1hnu0fk0xhsP@fHSkc$ABYNh`Inl1p@Yma z=QUkgGdte18=5R(DlvTM$Edss*|P0i`FKxepPs>(*^CB6gdq3*^Et`;C96cwi+{#u4#k z!UO?hx(eqCBhnP1kB*GKN1@;f$U=S*M>nX;rn5l^qu{a(vR@(%gJOA*_L4PP0ENpS$gM+kJ4+RVMUW)^!3$qblO?%<;I6Ibk64*6ztqolc3z2#l(60hqAtyG4Zz zgBp1`R}Kqa4u!z(laZJ0sHW)}MwC{lufs5p{yhO!~RW%X*p zy{3cSCGhNS)$ZAa0vG6!Cwdp7Zo6b`#<;uoNH2RrVj_*x$P+5mHFM5?WXsbDuq_zZizsl`^;QY$b%7G4l9EF zA+AA>zy}4@B-Mv6#aKMnkms6F5}k*8;b2@VC8^j!LJU5o0d{=vEUN-jc{lP(IDcFA zjAjU@+9@6D)@Mf44Aw@OimG-re_7<1e*kJ>Z{Z^4ks>Gl5l|z8$*{rp;i3hPSE|1^ z9Bg$Duod}+DHS>iOs8X2J^`d)h&7=8wL-Uf77*lRSH?yi?)dVEY3PHSN+#-~Glzmx z6i+~7)h_dvs*utoSM2wV`S{pH$WIBb+vOJdQVatIStwMS{P&9|Ec$KEg;uB~mr3ST zTMPCly|PhH?)`pF3dAQM!CnoSy%2%JzKh1^CP2i2pL>2FjuYX&3UB9>sGa~5<+bZ6 z*;3ERbr+(d`nZ9F`e@FDIVG24wZ*1M4*yCzKnvC3a8EmF?&b>dcs|eFZ&k=EPrsDrc7^2U^YXQD>V^7W9kFgQMWrQ z5EXHQsz$w$vM)dS#~<1$cMf=8+r-PWv{M_&HBvsjq1oGa1zQzdu)D*xf4BxFRUqYl ze?Q3FrU^(c2wIR#h4oVmrvKhUE8HK&Al?9HNBMyJ$3>!IKAY%01Y7(JQwsGsR4fAD zx5-k!VqNP2RXR7Ek%*S%jA2IXOV)nXja+Dg3WYgWA27?WD1LaITPgEI`c>p`K^h?K zDw8d{8rlDEV3}f7xgASrMvQi!y0D{QX`!A@xT{D9S>~oZ7#Emj$a_=_5x9OXF10cn z9yvmzf3{x1Nfle(0}vbik0Ma{B%4lb1vjQQ{S)7JTE~PGLzw!k3~d|7n++{W?RaR} zIkI-9Pna`w1CGxGnKQDSJvx(A6HT_CtP@!;0!~X=dKTeoi2Bu0+n7)yPR}MT5ApTa zhC^DIb}jdl*wR^U+}8H<7o$lo1oXn}o&&Ebw(ES}kM-io!Q1O-;aR{bZ`>6Q0A;2y zaB7HW3kHjbj3sg+J@#gUsZ`3GY5Wjn;t+|01frc-GL2MP1uaCP>I3<$fIMc(0y~i)VpZFskZEZt6^yBO9dCA>=f*i%P zR*J4^<3k1&z+O6aE_7JPvUbJyQQv=&Rr60M}b}PmSaQFux4j z#<&BDB)7x|+lix;Id$~*0eIQhm?t8#u>RS{TN&N-%X)N%x$Ahptj}*d=}SXhbM0hn z)wP`2yOA&XJoizX?l(hd#nWtq`lSGk0_n_Q>@Lda>Tw*pmKJW+qfn&`2;oqZPGjd* zG3s;jst1Vc051EI@#rKK$*`*S7Z2iU^&_*OIaWr74Z;mewymKW-|$vywu4QE1Cv-o zQ?MzkfwNA^$OTb_VeYybkksU1e&D&;^1|izTFWGr3y9f5^l9h?u_N%$_!yDknzRx% z$4(NmKq2qekZ4KuO)s=lc|PSZ z_8~4xz~LPW&)q_WHs(#p#Tg2(ySE=JL*EDZ81Ko7V2^7@d)U4G;|4U5HWbMb^pO&J z`=V2<6BV|IrY1SO6g23HN;!a-kcbQ@Y7uQ-*(tk^0mTvNRnw67zGt8#s8-5Q2}S{Y zkuqwX^rE;qWI;j>OSasxOPgHtD1#pBtRh`MsN*-gR;R+9q~U#8?_4r=H?!#4wDn6ClAu}na%KTpw# z!R3vwqwTRjHb6Gy7_yP65Ec{2-je_Dl?{TZgCWDTxwD;BHDNCFP{FquN#n51~vts#|N^zhmg;_}LHq?x0&hVJueZjRd*Qg_i&rBHJ#B)IQNmPE_169X*B5g>qyD01diLy@GDPap zhvti1aOqhU6JIo1P412IT=(8&NlGxdg>v%z-sbOua%WpBL*f)6;`#Lng-VR_#&V}E zh0?=%59A1|ZF4QBJua`0EyMdsR zQ7>kQ@S%<%J(fB>Y*^9VbVBT{^e&T8Bui5j`wE&BoaO08^vZI7!@tKQL`NFiCl`Tn zIdpO6Z$!yeXBv35Q>slBjU1-i=V_;mSx6F~I@a7)JRiX}pBu)z3Q7fRR9q?`Q$Dvi zKpiOx5vD{-epyCd1ExSomApxAz~)J3l3e?H9=S?xtU|Nw(8AfUWQZm2VsS&3*v3Ix z{SpGjA_*!|>Y)nmS0n@3t!m_A&0hwrxUjvxgv6FTL_*B(uQR{wxT|^p((?J74$to> zB;Y=_wbm4&+(V!BkB)%?yldUNf~?U!W`cp9H4lvhk+_z-`o_ZAo?*9;!h4)jQUtXn zXm2N(iq&%v7$Bf$jny$>1xHp?BqkCmf z)^xvD0!%0Trzn+D=W`}>l-CKX*Mu6LZi+9?}l%5BXIeJ%T`g`eU* z18sBThH=+s|ltmvYR zxbozTSvV4M`DzW4L~4dNR|0Xr1!*s&VL~3z?Thp{BG)V-#OpkWMb^NT3T&_;s91U; zKzQs7Xro!NBVZ#9P^pcI|5dra(i#;X7Q8w{r#)PT!=ydzKP>`r^agq1Vx55M0E3}T zlrLBQSIUqbwkUK5b?;{V_2zaoqheRo9$$V_BF(+1?3tylwUqlr#*2V~e9r2{n-t`!eD~ zzUku9k08>*JrqJ?D8Ng+h5|oVVwiz{17~v2hJ7>PBpU^NjHYZ_t&McJ;OR<@0arih?|N!!Z+&7$4!_vP`u0m@Rl@ z=5*=d>qeE{nro8F*|vuJH@Ik6VjhS_+(8#(#YgA`>XxM&XUWBRSUu)oS$5XV7I+^< z?eA!~*3Y*RC&AhXY2pPy=QzUEKBqMvX{RMN1^IQ~kut^#{V6?|Bwf{1GSt2x4b9)LEY7E(e*)YDbMU z+Top%BLnx2-yBjcK?GwfW#qZ%gqSq0XXv9r~ph+5x*@C(mA zUPa{EvK4p+0&6c1!AH4ZnPVyqE2Kc(JWcUWK3}^OXSM6;ERY1_eA#(qa_vab*6-quGXI*z_XY?%v*@zwr113(~n9e{Z;M zZMPe`0jF^`s$74do~Y*pQfzyOm1}RJrs4bQ_D*Mbc%Rl{z5MzBv=crlc6F&#l2=ZO z?zhaB5G9g4=B~U=Jc44$sTSI28YuAXQy>+s-X^rVLj@=U;WBuFv*$O>HDu*x{}jA* zJ5L^L$&A9R_v|tPMZ#oAiR7%ClH;=fQHmvygMZ7IfDD#xntPob=&g3M*8yxINV`K_ z>b2H>aBawquDPuHXS4V^P5K!2PNW<48ssa@wRC--oAs}{gX+DI8lFG{*kD{LUE%vF z8gKIzCibY6SkpEgj(6Z?;Pt~bZ+V+61YNIG-3)5RDJrCI z7O*>~CKR~L$=Q|zc;4WzEP038_DbJpzrJlWNx00DW>C$|^QzlED1>rH)qTg^0@I6i z)SCF^;Y*$e|EP=`F)Oo>Lj1Z;cz0!qzoOV+6|``*l(Z2;V;;ftUEj>vC5SnY=PmTvj>Yhh&lG5)@Gwb(0Kghy))ZLQp-}RO#W=d^oi- zUFGSb@vB?QhO?cf4b{AV#I#=>R%r3Dph#MSmO}O{nS+Oj^nPvh-+O(s=fnQ^(0{Bf zt0db@cfoM$J=&0oGc&~b)z-23p+`R%-!y0~drdcRR8TF`{Ub3?ljKy7$ICReG6}EHP`(@I zAz1=>ZwRwlh%}voT@>@FNQB`fcjCKBP3n z8civ69Ei2**iHf^>Ii`;ylBXK34H)-|75rkP=&@q(w$foj&v=@Af~b|v0)Bd5jyClI#J$NPOBmC^zCIKTpyT6g47A5Nu66 zqmE-SL@CU{H*n5d#Uk#v6Z^2q! z>^VEy4rTc)X&{npK1;r^^3#1i@YuO#(~~Kq@9Wih9{=OW>dl|w$FKi6Pv_^xEo)!u zpY`qg4hj$XEBQ=*>xlYO#tLcOU|=p^P(}K2RA`~F-e}c*Z>@>Z7p;_kMt^qj(erik zXsI%q;AGOSKk}S?Z^NGA6$|(uHtW49gBU#+46b?fuTl35j3_Mt)&O)^%0<@$nG&a> z7(FAOxnw98gk$*k#FfUjzK;HE+ClJmy}d2j0Bu3foIy;gRS0i3RI>%Y zwyi=?XeSfn+*#gkUxcsW?c=TsOzUm>nO z#y9e$Gp&oAN1wdmd!7H!C<^w}wWXF2W|iUd*Q^z|5w-h-O{=Ur_U(521Nw3>98 zZSPh|cecNlj+0Suwh{QrEZG@rZO-X zO=dH9`6eA{Nstk676+_FgT#Y<3RvskrZhMT438<_g<%6jaP;Vy0BX%?Rpp*B?DK>} z{K-=oc|6aZLYEo_wk^G4-rsnl(_LpgGV0WGn!8p37 zARxe*@+x)pd>Z^lzi!)C%jNEV|6je7$FT1ETDTM$vIRr$>x|v8t4|iJ9IAaBF4=;wGm<`a&uj-E zuLQSA+jNcOhWpfuT_Xfas(n0^r^lv92Z5E0Uria}y)>}LQ{~<4@V+I4)<7B~GY}e( zqyegZkC)XtX?OQAY{U8!th|4>sx2rvv#!mv^v^II)(yHrzls z-QeBmZcly0=cZ~0*}uEu)3#gOUrP6WLaraJW6xg7Q1ei*Od8%rgRsBFP)z!=$J!pa zB2dR<{l)x2GyTg++q@&*pa3`tp2Jnc6%TTOU-4X9R`}n{jWCVzJ2%+Nn7;N8$hjF5 zuewM0roW2d$$GS)S`QoZPV6~e!oZdnJ{{9eD>rK&rnB(`u}LU10V&h`Kz>uXZG3Q^+-+g#)yHrfxVR`S?h%5v5OSdvJzVihTtQ zyQ40R1h(H|+O+38^{?v0Wrk}cNa|;@SJ$M*Yj`}E(z7?_)a=)bsP2`q5uWWfLPlj? zM}zYR!Eohx*b1J-z-x#VYvmj7Sz5~UxAdOcRue5^J!FWhG^}fXQiZ?k@Q1hRUVD9p z_J73o@@^IUB3g5mB!39X1?*b>ca&sd`wzVX04p=&e;XCMHOG?<*pRzl)KAT*3q?@C zR(jS@mhipJc#v{CTxWqrg^-Kd#|YP!o?d3)P^F{Oq8W%2!1_dpAG&2I*KyhAj>Fv( zd3yc5UarE6H!0ClEwSx$pK;K{62sEw#B5b?LnRB@J&T1>P_xSH9+*6;5!^PfU2?&e z{%YEk^l!uAP3Fp4ze^3Z*ita_-Ddfv|7qB{R;icaz+z|n3gnjkhxV#1H9H-3`1mQy z>-hq*(k)N(ek$aUQEC~FqMa&AU;3yGPcgzSt;&_~v$m(OC zc|6#j_|pPHoOt5A@z^4Rb%>*l=Cd~9X|P2jEoaBUu~2YaUa3{3g*r|?dB7;gthRK4 z3ceLk)u*J{XC?YqGP$prSqq@n&kXO8GRI+;yL(_}G}#Zd@F16+k(lqw|z5~I2oLPDTNei3l!*(v3~wZTP_X%%dU(iLRbb0h8J94tlUs{{dI>CoK!zybNCuNhh+xycd`ij zlz8G*Mf&eiLAb$-uT3Y!MmeVQMP7%6*T1%4;LoL&;&ySeG$YpfJKoLppLXeG3$)5S zh(1yK7T5h{VX48r5Rpj;-MBbzq2N4rq6%8YhUdbN2H^0xaqe~_MV@xUU?3Id;+*xV z&I=oAGVSGo8VyrC-iy-~VD9wvgG|%Fd#)Yuqe3~LaO?X@V9=z5&V3I@7CA8(0>R4` zu~n^FiFnWx1mA-AOd`SCAwk_AcR|*`50{*J}AkYSh$!G5^6Id;O$G`c-Whs>)P$+L7xv%}*qL`D1gm_N*&%>=q8k?)SErV*!*v1j^w_!URI! zdACare3^D6BY>2s3ZpL2_4L__H0~G~vc3EXZG1ngdbgS z@tDh)g@hGJ`Ae1=25c6&7=wghB96d!{`})oCFeQF4-#3Aaayo zkALb?ubZ*Ji#zUe8(&~MEt#|sqfu~x{w_U|AqGJ@r;VQMa@}ga?+)-gEMq-L3Kzuq z1)Qd4hJU3AA`TTosok-y41ZaMoaYgzzAu6C_}PynP0}C<$a(~1fArX4)3?7R%&oKo60?5=bV}TW`4IkOp*@r( z0?u*jbo4%%eaiN4ON#5&N@dQhHKw0Jn1Vu>14?@`rUXmc6jUl9G1FInE=t2X)-#gE zXfw#6v_);>id^{|7xTCeX(oe#-#xXI9&c7!T0%jZ68do6Ib2Nv+O?gG^tsKnHleuaCR3pYMkg?_w}%1?U{3MvR`fff}I`ca@l6 z4yAV?@x}Om3y$)G{m99R^2lx$i941KLGp=m@5grCz_>V8y15r{d?q~ijs?^tF8|KM z1xskz+`6(Ut$9hOHwOH5b+R9n=_;$gTt`rG+BWW2A7>L~*kTH#WtdwSg!EJ23CR9pz$Xnchku#d<7#?WGzb-bBv2O;={EmxVv3@^frI33~?Eq zvt3{XRFfRzmwCejTbid+IYw+p;R{S)qhMDHG=v&R%7Im070?*?dNpwJwjsoQ-4qP;H16pJMdA>FcpIDypAJ4g~F0w z>iSDtAEJu-fjt~D4&}%`c~ZV1ir!3D-L+yxbF?66g~`M5n&wEnVa|>Ud+zw@s_U_P zh?f&rgu}VmkUXkRN>M~40LZo?Dd_I2Z(iSgf$c+`h*~DQ@4mAQ;(4M}5)-4LF7iuM z*tgyZ>i%D&e!<1alxqI?Y#+IB*zy`-D(0DPVV_~$^J+@ZSvM>+mFM9Di;uS^2KTEr zk=g#x#`R3Z=;O&Eq1Z~2h>8x_(^ej=m$2v}h4%U8k#iQf!1895Q%)FRxP~+&Bnr*o zkXQS}=HfG~=53@ondW!z^f5FpA5y^g9Y_ozLlI2w+S*5II5ox`P9?uNm981q%phdR z%&d;BqyZ12a02-ype)K}_E)WMpv=ra~@i(qu zP+Gflvs2h&$CjHLUy!+)5IbM zB#SfHNqj|@DIe~LKo7rz%kGLGME{V^kz#Uf6njhpTOWoKyRs`%m zmM$9ZuSurZkMQwzf4~)h?C|}E$jHI|A11K>CrJK3*Z=8nSt2i$qef$)`;}wA%gPT`a=S+&pv1 zw+^m38ryiEbRN5w-PcrUJOl9SKWxhyYjAoR|F$k!S+c{gIQF@9)tK&d)L8K^URhRu zt@W?~o>tv4*34e=%KQUTrFEN#qn-W$pLsda;qx!oSZ^DmSva=(wh(lD{oM#Qo^x#r zm)vr-xm0ks9r~F0C%+!U>>hVQt6({(`B{!_X1DsYktJgr-)c94KWzkPCuOVk-P(T| z6j8}O?%i|RXiM&T=K1Re3iAnBIs?zT12P>x$WUC%y*k%fzo&Iz5W^B56@tdct<7ox z`NuhPyO~%%u9Za4vBMmInUewDh!cdtb=rpO&fBw8FOEq@LZ=F6v6wh#2oAi}w=;)UL{)$%OMH zTwj(G;hTecay)`7vaxH`q%vYcr0^$FxD|5yc!mnMN3PWTZ{6m1SF^2PD|q`+?}pmn zGxyzDL9Q*uZ*3%p@<#B9t37l~Y_}tq0p1a{(&RKl2%Gb?)MaR;j!hGbY#nJUgj_gi zoR7Jt3{LI2=fhtaOfEcL+=0CP{)nx|VCq_&V7!W@!}BEP%JHby;X_OzUVQEmz?&9C z+4Kqhb=)>P>wtf$((6 zn(iKLf*^i(7JNDXYDP-GLkPd}_mQ`Dj0F zJx#o%6|9G=xYF+KL0SDEk08Z>9~f@NGi<~0xJd0%Qm*1sqqNs~j=o8L(X8=%wB>J|~4}rLf znRY`dX_!fn6(}CeUr+bOR3l4t`ymGrUogj-Vl!p9uzwQ(94E9h4*%&caFsQJ_m>aB znKJM`K`OS5Lc2{bC_8Ze_eQDl*cW*Nyl4e*(Or(hty?Wl#s;?`&==tm9l0)&g(=$> z@Cm;7?UofXh7x`o0?N*B1u-K-1ay#WN9W3HIdP8;jkC}<98at(zb@!wPG?=k- zl^Aa^bcr_=d)vVcm20kS&!E2%_39nR%7RxQPQuNp9YLZy&xie@frz(hnr~Cg9yw}_ zP6i@Dj@8};*)p7y7)i((wlvSil-we*4mB43mG&-lb2L3>PUR?(^$~fFcnK^LB#l7L zYXWkZ)R6&s4sa@hWZ?8qB!{3QMXOWua~a37%Lf1k1ua9p>yv?1L)mYRz- zs%-MbCh|Bx5={1xaz=T-v&{2LtL8ZZf_KwE*UQNIpYjoF|U3A;WRQ11oq3z;nhK z%AOLk6Cka;bkumhAv8eExsNNq07qoCoV!Mn#p#fw1GOgQB38Kcz&Oo-1+}T}a^h{k zhMez-J;r_dMZ-hV4fN$ZFWg@lfL33LfkH+_*3PH8LE?I4&Ewv<9xwn4Mt8#h5?B^9!7Bq98@Ety{FGM~&2%OOK^$)iYQuk;}u;o0mfp`*7j%m**6^G?j z(v`YiOoLI-XoKTv0x1?0#An-QIHPDk%Fv^%ZO`J$kF$FkKIYzbCFIBZ2a6m@(|eH1 zDEjns3e_m@f3)5z}!(4 zoAcHdURTr%<=b?_6g~+mg*dHyJN4e#gI?`d^W#dwdEE{)=khfgw2$3_8skA|$jRUP zb_ho@{GKZzG;~gpo%$olvp9ySWlv?xD_wd*?Pm2^E*1OyH|FzDJtNb$Z&#&0C!gV3 zXx|!s0Oc&7nQ?M~(a)`_|8nah*UxhSf3QZ*-(xsZcBu?fvtZILF zQ$M%olfa{By6HcpzW=0o2eABOlKq#duSQck`JnZG_zb(#Dx&*9g#F>{Dby4xuBz~I z!`%N)kU+zNNX@8}V3L@*fB3deA;P_4Rw^bUJ=m~guj_hKMVjM99uIPMZ}bVSulcRe zBr{a7dk{J$NL4ciiX%)&L!~AF(a`^hvng|fOkOekYhE_*c266F{MRA+r_V^Ne&2WW zUYo_1*cYTn;4X*=IOgg-OqsJZsza^X%0HGR6R#=%6rZt{Sp{d$U9Wwe*{pP}jepL< z=47A#Br%=ukIJ-_n7Z=2Qrc&eb>U7))R>8M6K6nb>TCiS9*Pm8?A9`h@Q`)9f0(LL zA%e0fk$!;o@2TywPE@Il7a151YJ9IRC9XPh!jMKxFC`}0GbE*Kv#l@d@9{cHlL(I& zpDl9L?ThGQb9H*dFSwkG`E)U!SwZ2taB0)i?91pG)XPQAvgySsQvQ_Kx395rYxgcY z2w{3^R`9STC$r3+nhEo_uj|uB zf8hD9@v?A$@Fcy&n>QAnS>*<8RF9qig~I8@tFM=!SQG6VA+YdXiT?#n8tN$CXlRk2 zh_P7Up4WboId>1s5z%{jB&wRBlGJZvsN}-tbhaN~o;~VWSjz|zcD14$%^;to2M?^7 zKS(~slq%-d*;B&VAs@F=GEzf1r5Mt*tEx}RK%G_|;Wd

NFm`* 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

EnP9HR36>|o)@tAAKZ|- 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Νy7;9s4mAMEYr8yK24Q9`1g&Ia2jo4 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 J&iJa5v?k+^A$>)Zi@MN?(&~)z5Tm^6DasAG z?fEI=Mx<)~$HZH~rZHpJQrJl>i9~*6&lx+3qq7e=f6ak-Kf*9kvhzEX_LY5juQ))E z_@9S_kkJH!vTBaQ2qRy%)5ljKcjA{SIHGu)m??qsdUAoF%Lgyiz0Mg4gEN}*?Ki#L z432Y}dzLF-WK4*;u~UvF&L}Gy_bR>oB5P8N`po%1PnaAQD-yZbdzC#FRao?qx1; zro+$E=QFZBxTAXhLJ0W8qIsL<9DopM)+T>HI8x0fQHooOH8??t7$H;<_s}CfiD<9rb--?x}e%zx`T~xN0rJ(X|gY}Y~ z*>PC&*F=}N4-0B~``}ELYZI(4latY{@EADf)~WXB?q{*bB)fOgrIoPoidcR*R=l)nVx5byJMVm zaKP5EI#lmkgDa`IW!8mjkwbQtd$3)FgPBht#licxoqf(2BOR)Yc>?!HXU(ViHOA*! zF&S6!`@^f3McXoPb*Ah-dZez~4J&dhU)PA(a@B*MC$EreM>c#iAhw1ro}PXxD#e>; zpT0`oK}V#Vtlj{R(O`d~Nk#`0s~9(;u_M}4HBB}%U)PJ>Ihqjw<%*+Il4ub_A&ig3 z^gHpTU6Wt~^4`4SWFKAZ8{Zmz;skjRphsem%wW@P$h$ns<06p}XQ=c;8^;z{hs6+9 zW+ah-m7t3LlFcTSzGB55Pz;vDa>UQO!(HGG_M$+Z%G&&_4 zke+$3_bHHnRV*+yiSkJ6r#}A29Z}2#$T=}5-)h!@w&bf+h1rzydWYhng8?O)d0{b_ zfOA-25in5kg9){*@h&!uZYUcJ9zSp$S#{QMN(~Z~MNPnGb7hBdv43Qk9v5w3e{SduP{27mz@DozJx-Pj#M@ zFdSyLr9SZuBm{NG(@`Ao#q9;jT3!1tZR{LzqVHFj7?r3*`q%~6%m^fd`kVP($|fKx z#XSYSR1zKPH&d5S4THi~&7%@~S5&7H>oE2BM3$=>zlDA;#_iQPruH z#9!kA@asc?#7`b7nFU*c=Oik2@OR2)k7Oi+lV&rkhTD=nK$H2^<&9++Bo*r__Fwqu5Pu!<_+sm4EdVLw7(x2!&}=;NTMfdKM82l-ZliLj-(D5EcY%XQg_R#W}jx2irttX ziEa|sW@tJpgxRqMhh{T)5aqUzN@{O->zzyTA-N^$`mh(lKq)I~bki>a0@xOhZUS9y zL}6Ym>Brb2tS!^KlH|s$yyQ8jt=0H48Eix`?I1-6X@b4py#Yz(ywmDbZ5Yw77SsM?he$d^x?HMteTB*!)s2K z3k)K`p=B52IOeZ&hxQi~vtJAQC|}15n{!!L5Ej2;$2ez(++Px{K9}QzaD)@XEXi_8w`s^zk^xl2o)qmZ7~hTG^M(_jNb)Y1ucQSMFz~W`jZZ&HcI$|jXull}euT21#OdDD^$MK$c4lND z++&>nyj!Mao0(OJK9?*N0PfV_8d@W-zcXbTqXOW;iWb}YAhsNcgQLCyKVOw#Ne61G zzwbaE92L}aGWUHMJFH?q!@yG&8f~jd)u15kjgqiLH&q{dA{VqJRa5E+L$Tr6SgI zXGMe=Qt1AbRaHAj<@L>pUFcZiwvK@MR0T)A{{}g%)9a1uhp!?@+*l&{q1(33h>cM$IR0;ryNlYRNnjoCkLEK7{GSh;ToY&`pcQ zfgrv2W!O%P*%yiagb%gJE^_90&Pf#4FVKQi_QUw4pWJhdEc*!;_!8WkRgqM7UP+|0 z)b>{5#1bYl80y#;IORbIG;!bZaA&zLg_xK+QehEWQ$M8Hpe;6{atKziq)#!6AU2M= z(4BB=9E#oFr)gXSY&`J(i`I^rlt2r7OA+w#j+8wUEjZLmJv7DzE64Nx3uOK<0^=AU z#?rb#`-z;zqKL3hPn2ECd>B)gJjor=X{T6bonS|Tn95wjAV%_lEM2)m1XDoTF3p2z zf|4c<(LqDk_*8M5VKZsZ8;`Uj_ulvy!PPvW4^N4dGFReL#*NftvDTmNnr`%oIn*?k zosiHq&^y#U(kL0Svzsf16CUKSrxKPuP(B>r#Mg`8OuQcy~a5ttU{V)+P6p$ej8~CW`50VBI|F zhCW3*3JxNCSuprgpYENOk3yJo8_+$fvNm^b+4W9`WeO-zd&A{qf?$KB&zE0>k}22a zYpkya#{bTSMaPt9W~Wvjb{C&Z`PQ`dn2gW)=}k2IA${jh8REvjt=OS@6;YnlVe+;R zfdu>xoQZHGK6FZd+`38mn#-WxH_Fxe(%*Waql{CJ<3P3F!9Erg(^HwKpw)yB$x;ez zUr*-34usAfk{VA+QoPJ4)_Zj)Y)gCB{{c8a$G=u^8AzcuC!Ap?TGVhGB(}II&lc1* zz!y6C`pKS9U>2ky!B3SvyK`gCw&PuVA5mD<8zCB;HhHXS+Atqsx+S#8V!lX=Q_$u; zh}g)3z)Y;td~rnv;Mi~wtNAWRYtdfvV+sdNL1?kw(gf{(D7!`Rn`nZ^GO-u|vJ~S< zBmxkxZ#(P&02f-P%7R3dL$2AlVh_KFd8yadc9W9k!0Q$hgN^(h;<5mLL5|GD>ANuO z6WXeN1WLQ)hjHlgdLdEJMS$Md20dcvT^bM87TB;}!Nol{jE5+VzUr;S5W}itM`@Vc zla98?ef26bbQE@rP9ti%=L;%KkVs;h#i%HWL$w8??RQd93*RS62ZJtj+rG5{AH01n z(kbDZUo(f^Wc9zyYk8J|$&C~XqreL+9SFT0Oy<#PhR&%Xc7t(`;^JJ*K|i z|Gn6qmMkwOvBJDuD!#y5^v|JzI>7W~&ytu@S?nuBhlS{qj(2)e|ClbMs_~ z2wdadQeQmYW|(NDN+MB&PTyq>+dqU zgB%|)tOV>$5Guti5A9)5cAc#^BZ}pZwgFkNRC?>=q!mF1nLiKjUsbS1c zAAo=J8P7vZ+PWIOpZ^%1VdkqhZn20}%nTu04NmIz*8{-_$^4PLSqwo2dX2aa-W|#tREG4pqUF$X4OV zCaGJ&yy#|VU7i?)UnBPN23riD6OK=E>`Z;sUB}TGwktYmuLgc-Tb#g`-BCoKk}IO; z1*dJ{9#)wKZMXY+=~Q%Qu>@cDIf~nodWCNInIdjiz*%ufp00kbs&_#d*Y1+g?I_aR z6i2B&+e)ayFZhAO?%a$P6tswOYh-5$wZ%`T*#mKrO4wCJZEe%@JxGh65Dwdgt5=HS zsUq$1;74Ih4d$^=E=@ALi8xah`WE%{ zE<-aKXDV}Y`3SZ;<#RbUE;l@`&xLF_?Gb5u3*A*L!W(^d^iT%#_yG;a=tS>*nse|M z9uY;XiDQ>7OL0Ef3{`x1FKG>YEfVy5+k(4TjiCG1z=o>rRKhQfLlt)6M2d{2f^4m} z2$wYO`|N=^R{@!#=#W^DjqVx1a(#X!iVo6EJ4r;BEG&NhnjhW3{MZ27yL9rAL_=DE zF3Y>!M3$M+-bfcm-34}W-wE;{@kP~%AS`dta+-x)6Ab4T;7uTpau>~4x{%84Z^4P~zY;@e_6LY#ae~rcei{Zj zeIOWy28p$U0=3z#CGy@|i}~sH1%_iWBXG3|-2?-)Xt?@oK>y!5SVo&3}dU2@`qI1$|Ki?k)i{ zPS&bb_)1Q;rB{4~*Ocd5=C8gj1*FpWOs=x)XKbF;Ny>Xre6FwT_@K^6*0(w{iZ6p* z{s>t{>lZ@bYw$zo<1cvg!s*EIvo4R4VXc>n(;>!!vHFE`St&BB9_TX zUJ%Gssx*+(!BY-lZG&EZC5B>55_&pqk#6cn&v^_?%^I>86rGhtNLKwr{Ey zct7WWuKIP469Qs9Ep~oX!lnni0puH{Z&6l$pz<^cX}+Cx63@rS7u1sPI z#0C%LO82{(&8JI6rMMyF!))*AMjC8>(pJ2XbkgMK7f(Tkf@|S7i9c*Fi6=wm(-s1Y z32#sLv@)<9E!z6KOGMv)pIb5YZCx;2>+goKk@dm-@h(MtE5wg0eiWcts%>43N!6;(%#A&D!s6Wfp5Qto$D^Fyb<_II-V%zkpS znJzXx4hzpvNH^uW2#a5qtz#a0Z+U&!p@{H*-p8%B8-Z?_d}foGyWW{&Cuhud3w@W!S+xb9x&<(8~uZ&%{*a;=J{fhL#g<+)Tq z_NNIx?Q?HJ$1K-k4kil!xdl7#)kFl^*5w>2kSUf96}0+tzU2xvkf<{(Fcba*sxx!3 z-5Kg3l#B}8`ONh2tUSRu+Nzh%nd7&29uqZGQqomKMy)lS z=0W8O4zmZ-nlKQMg!4pj>n({)u!!rZuWtx9;8}WI4~&*n$Bg5Xgnm(fD_!^KOXtyl z4X2x!mSr{G)2Bq5Ru7~*BY(T1LwEQzHH^!1kWDkgjpj+*GOKE8bB?a9hR^L8>{TR^ zM1aSu2^|+0g+1S{6PD;{z{QQ5Ijv%}=lups=Pd_{$0XjBNWJ$cdA^(Z`VS_bB5NhwPlCc=3c1Qfr3-m=&LHOo#i3( zpNpls)ogT@q=JVpC}m%)PFNqh?X8p-;pBR~t`A2fX`*nAhLINu%p zH{X6%HKs1VT-2j_2+XWoi&%>k&O6F$#<}N2sj8^xggN4di8LnsFziA0P2z{3g_qu# z;fNdd7M_L~2~P|rb#txF(Sv0oBc#evZlQHwtc;uddqFv&;;8wbsrMaH6+#sZz79PU z9iyusRTCpGgSmk9aBr7kAh%y7jX3Ty%*llP`t?6qw|WW7jKt>>Dm&m;I&L((7{;rn zFhySax=q^)ow&JNnZ&MmC{&pXM8n`q5E_fu{xUZxAr%O>kZIB8DH*~i-e))S=8bV~ z%@WW53|S#gPJ&rr)J6rM*oY+C9OWHca1Jt zE=;|PT&b(anmPkb;LGuN_~4MbUY2j0nsSF0E=u;#GYaz(Kct>gm0%Wa>!}O>hz30S z6e3emKr6oGc67Hk1$I%Q5`O@iD?4QwbQ6rtw(+^Dj*HS~7bPu*P0K~wH$C;rhR@;t z;}fMW>(shgiZ#PU(=m&I`6sZ?QXewYv8CpYs`Pwz%Q)@AJyKUNw~2MHSrbIC-$I-w z;%7Vr&@#K1U59RFa)G!lHbb`~YzfE^KFM7F+G6!NrJVPWi*x>IRqfA}5DoIM&kIlT z2&0T0U5?}@VLYX%bu}+>tp2c|x3M|CUOf504@sVR*_-HCqJ49Lz8!$amhDMVq=}NQ z%O3II_|nRaw#+~Nx{#7{|g3yw;%c4sEf-q8X;cY0 zWfdFdr^EHJPEfZdDlI5pJOQ!n(WT>fjpSd_d=&y!U#d20s86aQ9 zJ5N{lRklELJXpHi%b#0xX;MGmS0^Sg54V4L*3C1lJb`tuFHh*568WmQ;EYvX7y6yR zcdPJSkh#UM>{Dwr>0XpalJ(hJCZ9K3T|XO>kosdKxX><=-$J`%o(`_cC0L?e+rBlU zuit+s7EYl%Eq`${#I`;#%B)#PhZxholvMilYhgr`TX2Ni%Vn9P<}1o@w|ASrPrk8p zxj@0p(u~?j6dkzQZqqA~)Qpnx?(;Z2i=R{VCY&+@-o39j4S>^&Br)*=`C7 z{Ce!8+~KN;+>LM&rVGP@e*okuv~{X}+3Y+h-4i^~F+*1x;)Ej^Vb9up ztmY9>v!%j>3~c@I?aJ|<)R9p9N>tt#qp7=fV#younmzwXr;O*lk^V+yPZ-L1=#m&v zjJ%Kj%ky&KpgFFTRJHE~MbZKULy(P>LR%67bN1s9mkbM1tde*mO%$H-PP&Ob8L+f} zfL7$5&RfcUQx)N$&_q2P`s@ z?2(Ot$%+GFj@FcL*9v=gguuS|hb;`NZwMP)3o_lPBQ2Q6-P=m?CGBt;f)T_BPOVLh zv8coP6% zFQMs#w1&j<*7f~O6DtYj(Qpj+`u5SXWXqn6M+reFPfGxfPur8V>LcfU#1&l@tgsP{gK)*gE=;H}H`2gPtbSe^1t5yXpETM(b!D&r~F=M-LP=_5!x3N!U!v%Ec zvNg1kxUud16d3s8-|t$Aqo-ia{Rr(}8&}SM5Sb}q2}|!>G0*dH_bmQva$4A(zVF-6 zS!QL0LxfIU^2*aOcE=k8-LoO1Ec`jC!Doam6>RFPq48|wP_1px3^$assFPWe;rU&A zth;l8L=*TRE&SZ3aDP-55q$Q`@l%=iZZn#>I#a~)_UXzR-E)RWn$sKRw<1a7x4}}q z@!P?0+b;ngpcw^^$CS{gA!yc>E*1l5NaaHOjiThm2Zt1E^teGGfjiWY|F*Ulj?H~+U; zy1+tDhNl_d@03vVeEQhEHoUotAdXcSw8y!2XC%%B@3437?svsAx&rE2qzbw}^?x4F z@srL5#+liyctX43G(a7;Y*@cXpXWV#&bA8u#xiB06UO{8Z0#q=ZJ5x2Y*@CMCBLwd zC3z8G7LD?K`TBO{w)vDi?aWhhn4kb4N#FF1Ro5I=ROnJsbfG+UbhP5I|YOuTGA;(pUYnaxlrj*X?0fx3Zl zees$_VqVz^M~&TLB8i1hYEGz2rs$Ho&Xo4rL&g#fm#|AtV8-<3HLbpo^F~tHtfrf) zjmlw!=qhY15kP`l$?6PggzQmPnEPN=J#d|}`ewl;U~q!|$3dCCH)qxSlOpsv>XEiFQR3D4awr zSzhO}5fZ0*0*0JxH9O=0qF>+g1T@t`}kYDX??cK<7Ky5U)oiO zoFhDD#goq!DhJ;Y!L%iKy>klusOFp){J97Ynx;N!E2#GF=A*`!66|06kS3Iz=tUDS z*I~rPkT@wrq^T;^hCzgwrxH@%CMj2Qa5U+eyzWw~R-pTC723ji?khSDi>tGVZx!BR zpUa)=m7|^T&1?|ynB!%=@n0M}q{2LMuJFzfj$T{&@Ti>aZL}W{=GS8$`_@0>OOp%& z54<1>6%E`mTfcdDG^MlaF*3{XR}xnF(ugCwx;1!h9cLYvd|NfiK5fF!u%A3f_|yj% zHhZOZ@bm=sO4=oTylqm*svyuB4V$zrCM%xv6}c}3)!m4j>35G~6gt)L=$N=Vzw%-Y zj>wXk$}oRs4-|`MNg!$-NjoL8xyz{m$r!4EU@2#XUq^( zon_bO?!{K`dQUxYvKf9pM0<1bP$>3fLZuQ)hZA0iS8H$m`sv3$q!PoV?d;e1HW{cW zueXUR^_%b|4c~iio^PJB^gnKlKW{(mY{cT2(i%~-lnlmH>k8~FsCH@EoKiDOtfOMA zu{3)*GcaFI&RK=bd|0Qh$R}#&@I9Ps`4y)LP}(s>w^4o$v5_R9ubDxj40Lk)x}?Cj z>}m-QtkCr*Nxi;fKn29_HzC$Xi|br_ZAJ^m6Z$R6`&Ecbr1tn(2bS^zsyn^N-=1BK zM+VR8*ut8M$jI?nKXuG6_)bL?@7^aNKkTBAG&&U!$UN7oayGEj)RKe3)3_T;);>6F zmpIO$N{F!!Uq60#W14OzNSIWz?GKgU*A(r!C2z4y2sn;7zXHVz62(2Vhm6|zQMuiB zDk_A9Xt^Dat+=!`YG8PG@J#G*yM!lt@pRXvdg>kzV=@GX^KAopCvJ7uHo84?J0x!( z@uiXFNLMgmF1%v!=s7i++h(kxrSY`#VqMP-=Z;)$jaapa2Efx%8ZrP{wIwMq zNIxsq%k+}Aoy|W-VK(-~YX8Qwim!C;|CJ@t66uH=6_%6W{N}5fEToy=^}yhicx7CK zuSU)lc8lNVwyeWcg0*>PKa#nKi9J`N{;{7oJ62yz*y@KSXbCEYB<*Psi&x(cQY|UL!a4bZ^Ceu z6SxZGj2R{Ds_T7e>w0|GKM}(U$&+Ld<|(JN4OphHz>=UaKe0KT@2T4p;)G9M7${`Ufv2fkuvK z_IT!}`;el}+X=S7s-hPV(4kDT&eZ$_hEl|r>3QF6fU#1d?R8%|-V)HR+|VWE`1n$M zUoPntO}pK!DRUnWN2k%j>z7`c*C(5gJ|jjXy4je(6TXvXw0 zj4h6JJugMO+Jd+>;$D)3oI2eoLv28bXoc<@o2qg#fg}SuMSMc`S#8S_YzG}L*J0ZU ztfmiZqH{cLG_Q}ui10{G@?DUtK=?UJQhIAf z+FMcd4j4|3T-R$xrW(}4Ohhb)O;JYLXW$>vYs%VuMLyjyu;*S-OaBa@tRwv6_);p#*>=* zW^AJ|(G*%nI{DqN+{$C~_|Ndb?ci_#SK3PYH3Yde>j^R-G*tO z_WeABfBaTJ)!(L$B3+MM$CmSFMpAfbhD2t7N|~&KN5@vw)lsiuLtWaPP;+y#x2U8p z=BXRMk#%m8INF**fP8uX;d=&RTGIl3sgIKUohTt%2o%5qt@lZLA22!D<5sBsMIoUl zbydfBw2zLhVtHmOgR6ON@`(3Wxsv8N0FxR9;nyHN8c%vM!%FyUgn7ovYx?Y%6C4YY z{wzyxLovWaw>)jq!t0&5=jD6gGsDFv zY+OnQolPvLB8Qt36ENS35OM^4rrrFtM;$C}HE*Ue0`}CsG`N%;p{(1U!5wshWK4mt zgNZs+bV)2!`NjhGa~Jz$FuOH|gpJObs#T0nvaZ2*#kWOh`$e>_nzXZgTq%6;!*kzg z;5F(;^flIxB_3I;g8Ja~n~YVAA-Hm(W<53atKby| zpD)~6vd;>ZXR(Y>%L4V@-KZc?RSwC%p6#dS_oj@lxrL5S8Q`ITV1y8X~G z{83WuZ;=w_zIM%Su1$^`V09A|@&R0KH0cXX7Feg*NFHFzg|!rLwOLdY9kNopGMK28 z{g1pbgcp87BgfkjWDq)7oP_beJU1(eweaOc@U)sDX=Mnhwg!LY4pZYN!&`$7Y*V zDxMl{7AU1e!WJ_Xc}&@TR;cj{+M=Qc4W0DCHJwh%ef&=v{s9E}dZ@hI&?_dh2z~jm z3_IXpL=FcJEzO%Ri$#@7ExiY|-(m=$h9fd%bEcBEERiDku#~O+v0%MEAd3r+`R=m@ zCPXixJ`;PALFID3pTEp*>aj9S?5sW53k=vXfW}8 z@2=C%HT5bia%q}8CISgbG73MIT*;?KPufXcbNaq$1Tu9dk0q%=E(zYe-J-DcgngO8 zGENWMce6w#Ns||I!f(>*0G&4I-5Ma>V?G0wBr`+gLl7G;mJ!yueOcsg6M1_!st<-meqSyM6@<1Wj}$b}IChDhxB*?$op%j05Kb=A@ zeG+})=78JX9(9f13MnHYO7Q(Ow&OIia5`5kNejz4vQbJ}6uHPcWbQ4CY9Q-|*vsOc z_Dvrx<>Hk*9W<8bw%`PjvSYd7QqeB7ayo zCbW+&%cU%{i|%7_e44b=w^@oZv9{A%mdI2^j;b$BE@3{lJ{w(4&OE2c_4=PMi%WS= zu1M?w91Ru&d#Bo&B-}WLO9XYEu{&8Sug_9hIv27SFgM5{0~s;9U(J)7(aXZzTg4IO z0-%U{G#rbH0D8L0MHFQ(pCFn2bu(H-yoZk+M@C0V#&Ej)u4fA}FeVD5i=Ut)wGu#6rej!*Sqevig4mK4X?0Tnn#`)p3K_$HLyf#p@FEjo8T4S@hSojeq z!Vhi$Hl(z@52K)SS;Fkl0qf()K#d)jOEy05>eD$Z+3aza-KWJ?noAmpkz1}NuDtX| z%VM$%OYh9?)no&+k@{pPY|D48JZfC4pbD(*W|^!02cx$?=WI}H{h4O`aRJ zeDL5P?cOo6tNi4~{=sb3if1D*k5{}%tw$<9sqDI=+NGp^n0TXr7cz=Ux@aoQsPm+lY@Cu9sDE)^W95(GJE5 zi{jHvo>DGqAwKVn`{7=V`j6VxNhwN6ODkds6LZVT;&aS1{F4cNUa^)&G>JV}S6rr3L%2Ln_FniK*!AzrlaN^z3#p-cl^7^~No zaMPI06=~^hI_T>e`;RsJRBL4*V(SX;sNrAHxh-5-+hQEd-z(JO`txS#-`0-vm0KiX zb$03lI*cW|XDj4C+7IN@k&>Uw;8hBhi4Cl)k0Lp85r|aGHDAA;sJ*>f+-fvBifu6FRoGtsVY z1?tS3bx$-EQ?6`Ul#Czcqj|zEv1+w*L zS-KJ|N_gMXS!guyvL`G1`0ni?=476*_iPah!SS&p#pKs2=LtuVyfn8g#ULm#)FRvAv4$lLglZ4A34Z)hIw)hIi0sPcrxjr-;(1vJ9eC9umUINhhG@rbO4pi&$Zqq`wAig+ylZNZK|t6+%cm1?-2@1ZhgSh6V9$XaAw@x8E(CxjnQ*l@SeX$ zc+acRrqlbsgICnt1tB7BVUKb173`%xbHKw#Tqnk0tY3ypNBWwltZzsN-U2SgUXRu) zOX<*wjb2+~vR_U(N#xwA!A!e&@t7PgR28w55dl+bk$}zNB)Hk5%_N6GQEx4KoMe!5hPt6kVKtGVe+Tf& zOCmTZRVj;hQZ*KKwg3YbD8cS5W7kFou<0Nk@JfxTBkkby5Z>?)e0IWf3!52Q{2olP zrun#ZsZ9a_o(541!x`RjmOAWH)Iw;*4enmjuX7h~PsmaE5`VgR1BKFB!jL?c@!to< ztEqTgS9bw6<54By9Q%*TjlMBQV0udq&reU8VDFN02nzdREX$#I{PQ^yCsEl@;04Pi zpvKkAr0Se!3e>x1Sz*oJHm4(z1F-uQ*J zo&ke~qu;syE0_(YyNYZHZWn*ibs4~4(~xRO7EwwQ$3zxUU|XD}+m9TQ5@?PyJ!FI+ z22h6&3z|725=KCuWLR<|%ceBhto#YsjQgo5)zmT<54^FPOexMA%a$!YreYmxM@1j|mk_&~CihG3im9NCX-&YrN`Zsr{*^Qi%MfB>Dx zIi9Ue@$lQThMRQ4H!>q5Zh)G;l&i@l-qfRH=3A=$c7LHg(yOA|y)et6_wRIhE&{9AUo`|^AFkC5BPU$ozH z4T#n#fla6Rqqh@CWO- zXxe{=i{E2a#v=ulx?n)kKu_YF+eBTdr3xEA^%zPNRTCas ztuObit?pd<%{lx${bewY9pw?4VES_%7|%1i_M|)*dRW#}jsMR~x-~h~0Bc%-dFBQ1 zyv|4tP5O{Y8zBlXyKV$lZdQz2)5y@K5H)9V*<8Yfum{l-U57Rgb>XEOAa{(Lo(G_* zH|?RckDzHyuKI0;(u$3XHgyGSetIRl|b;8UPyXdT!?ZrV>}Mf&oRNM%l^z@ zG}!FOYJf34dmEQLwahFCjRBxO#mmNR{Z}ut+Tzo@YJ9IvDZl=gIaDqSowaGX`K1P8>2=g_9TiSlR_5vQQ8s zRHeoE=-t;C50RB8=?73e0E&LK+lsQjXc%RHQUfwv_H^!}HDZwlrhTJ*yN6cBaTrly zV?cGYm)q5g$Wwnurq{{kN6L$#ILP|=mz<3n)Iw8SHZXM@wv;WYN zyGWBmQ_5sq^dEYD*Z5e@XlMk(@fnGqTeIAa2 zbaCH=cx5XRu!4r!M?pSF_YC?$!I0U{C!aV~pwMvEq5WY+a=O)hL-VnB9{L{*6zMV? z)y1|*t?3t$xSMlMhb!iX=h1H?5LwM~)5bM7Z-{c9C*WJ4xbC8q5V*X*V07$nZ^{r7 znO0I2_5#oRgQm&h0R(VrUql*G&7vzEirq1G6eG`(sV80xVqA{f0RFrF@KJzk352i& z^U~=uBbhM6mY{yQZIEy^QD!ALgLCfh4KGEYj`YijUX%*4dC4_gjPLgVrx z6-E4XnG-mkw)SwkU)%e{Y3&xq z_^N@k$SsF`8a9tqkr0uoPlSiDclRU~fz#@s$QxE3ePL<)3~1#9tjF4(&qAF}JMXPi zbJ5t1d0&7T9sFahB&?`#u#~OC#4Qky{bMT+l1S0|iqoT!LeV^c{|Cl#i)uayu70&`Nle3lZ=&C!r$ zp)=3|g~a`SN%~zcwP^+%u9UoA1T;wPWs?Zo>JIq)>LuIe)e?(%tSRn2t?Q zIJn10TuL zi;j74haO8{*ss#QsR7IvjjcU3XAhhn$)XDh$X+6-VM;}53P#e+2#T8@lV`!^I?zU{ zYH?E<)IL-OsIW-_Dt>6Gywem#;^brwzFhVGWJ-Yc(PYa>k4k#JNSmABTKu2zSP`o& zHT8;wR4$s8>*YJsxXPD}YRH^jeGy$C0aGTsv|3sdsS~2TC#@B#KVphVdncc?p%N%Wf5}Df%SV z?NN_GRD{2sYAWy+t=P^6%L%?C6dt70m3U*Q7jSF4J=1l8LDz`nh?{;Q&LsZJi*SA>ex zq0w&JxXVUovB}7JUWS{ScMW_b(~dUOM7_+jEm^Chxd<$u0JjSogx~MOdiI9oSGdql zJGz-Thtg;&&~|wkTr%@8$;+u{I}Jg&P|?L*=5Km&`(hf{o#(1&Scy%U#J-+x%_l!m=Uxe@mD;|-1?^e!$*gapX!7d1cT2yt2YzD>gN|L8OstD0G75xvU zfBNx0e=F1a4eYcDSe6mo3WP^ciQOy|X?B@1p^!ybEJV|>^H~8k`q88Bobg-PU;ASpp`;-j51Nfc-X=Cx>P(1pFLz>*c?%>w0;}0N) zhq671T)TODHbo-xQW4`>l<$3tyDNd*BF)&AT0G}fnbP5XStj9HH9y2fYfovl@1=H# zy<*;;3*6zbFA>vxgb5f+hDD5=1Yne-*KQg^(b^f@kAiE9#~z<_v8e^#+({(#gFg&} z-4wQ5E**?4MF#ColM`C>dhzt$mpKqG;GVMlZm<5WBmLx3Md+0Uqv;WYOkV zU7b#7fbB>7Adc~v%?ajyj5^_d6U1>8ga=x!B*6%D$X4|Iq?}0#Y4QeH1$%rV8WG|9J!PSa0uP)S7RR1bVY_Fgw>3A?e@ZD z3lJgPwXYc&#PjYkO%DmB+$7W=-4w3eoq4E2+RdNLevzye)gVdz$PPbQMOMi9rxTR4 zzo^d8#1>@Vdt18!Tovo$_#Fo@PxWPOB;eX6kuu?d?shQR@ZHpU;i^Yq*Y7G4Gvs}QY>eBP`81l76O6oZf(1iiEx-HKZ`_>X%?4jaleUQ1?}?U$3%`3%=r z9bGBsw8JN%6B~6@JRB~zrU$p`?|G9JJtYAe@RJ6u1lc~UV#YonwSw&9 za&GIp^r252Eap`*+a_=b0_Q-=7b=48bo<>@jb7_VyfC8j?wVdPQ~DYmI5a*w2%{rM z^djJ2hTEv@N0WeL93M0dp!g-Ssq6prUhXa8%c>J9i$m`%>`*SeoVmk`la>P5_oMd< zIq`Bkj%J7;6kVP>9Atk0(MR(<3TOmY#Q=`BZ_R69-WJ#9^etGSI0BF8|H7ZX50g0v zV|u2aCkyHHv>gRi=Cj#p;Ve#v8zr~FV-N)sUh*sa-6i74&iw&V|CZ*>^ovjHvw85M z8+G$Zhq_p7Z}VuY+(bNKren2h_(c>sQE{53S$fXYFCQ|N4tYN4UwpH`CDkf%Bslv>!M;| z&9h}2CP)4Z-WnoU@LamW3gF534#3oY(`fXMt!-siWJiPl;yF{fp1-H5&nroYlFn&C zv1_maEm2ZV0w_sJ<(F`EP3kkqo8TIwujsSOtBA!ly&^SxSKp;r2}S<>@`hAUTz`MH z4&Cl7kMEAsE3}w^XeI@4vWQ1G@<)OnBtDJIdRdtaf`#a_3YGqTW!8=Ol^2$#M<`4v zN*__d?7*G9^oxahxf83L<7`{geoNtIsE+XiE)$h7@h0F&X%>Dn=djZw>CYj5dz%ed;h{jBvCzzYpy`Um2;Rnf z?QrYXt>u-y<={Q8!b?jKUacL8?t%9qYO1hjBV3Xr+xMf{dU2BtwH{W&Fl2dfZHlU>qi@Pok_(JDI z*78`5@h=j)^*!al6&^J~7{O0X6(=R7tjz&}L;F!GGQPZAi7Eh1{ubSwsjazV)f6C5 z?}zI5)0~J|ajndSHkrR1W2F4v0w+ z^isn-2qrW^5W1rNSuW6suaaS!NaK|V-)d#Lo`?*{EsfWzQymi}G2Q{EJ5#%kuP?G- zExU@J8UNrn*%E86yofxPYK;^XY9<8Pb_FZS(QndNqXBF4b@)#GYMmC}>5vs5(0Xre ztD~h~&W1WY>o}>La~M7nIaJ(xh4bg;V7+p8hm04t+hLsz_<2#kiv@2-msmPq9L3u` z`es^2tr1miDJVG8Z%wp&fL85@W>2TybP{b7j2a(mI4&B(OZJ~@?ws%AoWI$~8{)qT$tNWQ=$1hYT@ z9}U?NMwq+$gs{1%R~bWGEGpo)IhV{_D?ok&-Ag=`tmwzGD>I79zZkPvDTST6urt-V zl$Y~T0yIbf!TdqMyp-H1nRkuP32#T2SUO)E#SUurrws^riJ93-fBz`6Y2-rwX?i%g z&nF8yYq6tW4uAXofr?K}zAwGIPM2MoX?=+7QBmh4nmST)^J4ouJR1}wD@K^WA`qJT z?`n-*k1A$@j7+Nres6QEAjqup2JdQNWuTRBq3nz?I6%NP@_P62Wqi z0wYlpcuw_ZNqzZAta2X{nj`Uh|SW++D8i%-8P`1d=iz4RCl<{T`PU((I=7QxGEyoPiEHK7< zQn77#M>8hOQe0h?c`c^D9BvJ(Rs8;rvewUQxCgeHIIIA$D7pUdoM7pxO%O7S$QMjI zGm44T_5(U78|)tWn~l=V>0aTMBo_`FRZUnkgE$!erSLAd zRcmht)+aatqk5T)RvNwmyy}gH9&Vx(NE|@LTy|6X&Iz=m^N~CCU6R^{+s<(7DFVD7 zdw*dz6fkIU>5ZfQl;wPcxS8?yu`v^ISKluT7LX4oo<4V39zT`26^l4nL1_Z~BDbsD zIet*BWSKcm;@dFY&UlXliO-UW-+1m4^Gei>KAL+Wyg%6@Zc0Z~KjXgzbaF_e2bOa{ zlQ74!oZ~S@du9**cCxVWht@@pD;<;v%TqQ14IXsJ(Gb45z!YCfuzS|>GE#6h;>5?n zp*caXeXKcrYlr!X@JtS<@}gf@8sHYmn7KiN$Gsn=-cVh{kN z-s~ck1-W`STV3FdSd+k>{nU6teN|9_q=AzBX-_^(bfa?Q!T$omr>6kBS~<+7Jwumw z)&oAbbbj+mpzC(oQs9}9-gI8eHZY7&EW(y$-HGqV1!+88aqFfo zW2tJ&MxKv5*USU1Bv$bBDMiUZtnG#gFoB5Q%%zUQE0O$9gdK&zQ&oRB+&RTLjar_wscQo{004GfH(!-6PElx5SlZQHhO z+qP}nwr$(EY1_7~dflTQ)Sy2hMiD2@-fPkRnQYaENV>$%QL$&gBZ=|svs)qR8nc|C z6ATke5S}uNMwFs>fnjXphQM#Qu+#Q%JMFNFl*8YD*^2x#@~C$1sVBYK*5{jaziq$z zx|NWfq9*@I|K$S@iVb2fxSIZNK*FG^(Ja5wpub$4xC#j%66JF>HUWYASZ$c*ODoeT zM{fJ>I?~8`8bQgB8y*?-qrK7O&_}GEB|S4<;82Q@nuVptthk0(bOBpDJOCkYA(<)Y z@L7Hu8Pr(aZ@aQmZg&m3PBGu4^wu+qp24~nahoOmnXVXp`*Obmqj}1MsSnkN;t-B zU{rukf#0U9@ z5%qohPM})hp2M+}kf&>hS?Jrar8ZBm2YuTx`~-|6oV| zABxsOQvZhIm#PjZybn)BR_QrKu8suhH~W|o|CH@Xtw#uwfy~xlGPN(b>qD4EIvD@j zb_)*2Mwn*5mm=143Gzc>_UKrYEVcpvZK%k!0LA>1-?SLF(6gWjARte(Z2r&ESObo=-YmPjD8Jlx;iJv+; zTbrF9^a@=wLJ66O%s-DHvpohfxBE~8QdEJY-G{r4zj4^ni=Xr@Z6QFiTRJPi{+Xxx zw37OWL{`*Bs;yq4>fGwlteNJmSl2d>y~f@MWUh70xoU_N1^^@q#U406qh@w-$%s(l zzoxN@sz_x~q#t3T|NnWvOW-h}Q49tBj1o9fX6~_yh1R}8RUV{&gz5nXaMA7PLW$3+E83hvp4%`e1YQk~Bk)YE zK-myl?q&0Wsd(`h5A9LA|IIDNCzC;Qg{Ed|@^)V3hS<1w&T}9F+NTB%Yo$CE;~vd` zrzvLH?1XA@IEJVs7iV#uDUM3#Y`^Tl`BN`gp+@>>G~q{7PZH9HL>#BYDNoaPh-cxu zW``QD35xheV;WhE8`F$~P_;pX+e>RBi5)ld zojW_9#zs4uTcsJj+Byzdzpy8N)7E?@dLGuP4RLN}ewBENh*-Ej!E*&cYJO4iwop19 zJBLwVkx5iK`GiWgR>Mu2bl2@WN!Yc6TqeI>s&2PY)xL1EeY_5KCd#Mr;a>8lHU?lU z^S5p!e4S5ul0Zh-)>IJ2N>~?J+_`cZzpNdqHr6L(VO*pc3Qv(nm;v~ai?U(JI@N6p zlA(JSf?xMU?+#Bl938d@V}P1LJ~Dedor8)iUJJ?{mE#V3-^3oa+1W&nBWp?1v4Dc(R!!nt_7(Mr>5 zT(iTCEOY=qP>X}sz}tq3LOMqOYC#$@-la14$Rk8qWV!(}DbXO?y@r0<#Yh`NR&z*! z0sYjcw&bm*qXZSFTFc^n;J}pWqv^3Bi*aQ0aIH|2`AwZw5IqO2^|Vbzb4+C)OU=h%*P#bKK)VK>|?MV2fP1nTh0cD zlHW(HI2q`@n@ye7Azp+x7)1muPc?_3vLnBN_8pWoefIh4(Ee}R#M1%g=Z$WCRwiKo zZ!0y3Li2WhAP`g*xSJr+_6Bnc9UVNCE-te^twJWF#VC-$Lr3 zzr*)OUi$_JMtky~cfUdyMwJLOdE~TI2ZLKBmMEJ5E1en)oIYbOyK;sI7ba|-|9lr1 z+d>r6&G8KoCPNDTabWrHVE-|clF;NGR?F(eP!65xkXFxOl>Iy~Yb%b)uU5;yW~zgA z+9P!s;#6Vxp~?H9%-{!Xs7K*Zr)d7#-_HR)GONj12ho@Nm~2h7ltXHP;glVg|Q?e$?qBiY=q`wB!RVFQHYiMUDUb3{Ap94X7Sg2%wm zum&0P`YETp?lGc1IX;(rDN;Rqog+w7&HeIWHTT0}Ng99nCw z;pOKi@Nf+5Tj<&C4)9q&+FlwH*h-`5u|&k26D3E!GsnX0e%X-C&EfAtZbKKaehd@< z8lX9G4dB5YGn0(^6Xw8~WHzuLkaI~KAq{(2pWe1DF>9{*VW0v?9g@$V#LGho{DP<= zC-&2jBpK=6#jI&08J7uc*{f?^(wOF4PyHC12b!}I$nk;<=(Vp~>j&HZ6z-@?R5@*) zEPy6Wpv7SJ@r09VQWo}*D?P{FXKozXnIbgIoLQoxIy$X@0-Q-x-WY=KF>hbF|5bk| zXbG}cj_sN9@*-3IH%lm%hwYKL#8e~GnJcY${pHyfNLmuo~n-=ll zP_v4brY&@Z5cp`J_;v4(lE>g@mK5>BB|ZsZJR`y8d+jk(yxEvjB^d|?^rnQ>J;4pE zK!wZm9~4a)^xds82^OhHx6sm9+LRFYt4MUxTNV6P9JO$ocTJeJ;yoz<@x}Jr&gxYt zaD?hhsz63Cbfy(B&sC7SQvQfod4I|lS)O~q#TiUzo?6?>=YgFmEL1*#z?uu7+*peW zyb1a_aD^}+uWz0&AKGG=NLG{quEcG`iPGiBsRIdTswn%KVY@{lD!D;14;`7a^~)t^ z$4_M61FmY~1`2cY0FR=U1@mkqMCC$m>&59aRiE5w&x6ocR|H zs~db0&Mr9EgL3Tybp97j6EoxgrfFhg`+qe}%xs*j|Es2nm4TJ{|4K{$KQv8Ut}5Ho ztDsTHIs1ax?(We437S~oV!O2+!uN0Sgc|m5w7JB~+yqV=^D+ zumGk3)C-D}Px9OThGP$NG577Q?i85C6K?cj0aVatXJ2_@V(jefU{2}eW?=}#ox!}b z``;_IG`Ik_1>#l%-qghlVDIS-b*^RhPF&gmeya!ac>yQ1vH)&!0{jII3;aQC_ey=h zd%tr3;@7(o{yX#LH#P1)2*|HGHZ*a0e?wDLR1<(Uu{5`NL2hF1q<2wsVskX|z&-um z7yEq5qdrI=03y2hI6v?4FS65jKigj+9gLTLW?gK0eB##MZAM~lbn+R0%g3E*cEEKG zO>R!^uPP!0Lvst@ui@RZ`5ev7@8cxoq;zC-WyRuDAABBeFV49xE;pFD`&oa}-!d{% zsxQ8%9322OG<3j)uf*uJjHp0c+g(lW{@;Jq$d9^Y=81*i2mkTSO}JyTvzI@oY|YKg zte=IQ<#=}0_Ffn02cs&9a$QTKb9|Rwcn1+zm7f8`-dj* z4dCe*o16f?G_!#>bPsxRVs(OnjRny2qnEh>f9?C}8~|l$Y28hAIjpffqlNc|F4*B`+VxHQW7tD&_znN)oAzJyTpM_`PyX=r^-nkQ z58v#yKjlBXg9iH9`N4(xtiR{>{^}pT8@-O*t>?rp*x1WzVEo&KK?0ahh=lXSh z_|>Nx*XNU--%D!ltv0lO@~iyadD2brpBVk_`|K~i_NOVh;y*U@mv?V+`2*kYo!q0{ z*xTUL<^cL@B)+S5a0C7UzMow_!9VRTsPoUJxdY|-E&IZI2I2aJ{@FXKe?L3@yV5fL z{NnlkN_g&gS7mvzGY%VVChFki|9r?cX|NES?_xf82_`drOonm2V zn9z;RyY*GE*xbYb$g%h0lW)Y9{}+_?eRc4>=KAhu_hm$mqy5Pgwc>%mdfsZAmfI21tk2NNZfP(u=*XYUlC+eqfg zP&RX=<*c+a5?h(H-fQ-{LshGOS}z{aF{q9hC_^I<+I<_hh6NXJP><1%dxKjqi< zX$`yg;GkQe$+EedX}e9Rv#B|A%5Xd(H11b z9a5e(7(wLWXV3_t#NApInJ*%nKQ|lMO7bmIY@639Je&E*n!+v_!K6#Vayzp(u5 zQLXrD+?Sy~BM|SH=m3tS`MJETpe2%S8M~^6j~w-1t$HZ>Bm72gBs4K>GnmIDa~~nX zSCl?){M>7{h2W@&8@*IiY4y-2DT)!7l#nxm%d9Bi(Sdq6{x@P&jY?RN@5v5x*&!ydacoc7M;O0@GVRyZo^A0~gEi@@?l} zZJX3*OK%=*_4L!f%UdthvLv(tS}gk8Ezep%8)jIIH<=>%P~T#+U%~>}3h5^5HhOrO zl)=2L@rH3R=5iyx2WuQg3!34p@*60>xA7{#pWE;5(F7u-;BqROkkKjDd?Hk2a^wjr zKh>6s1oXBIZdxI?<8)%eiG10I1B`A;3QBZXD_)&QxnxJ`gg4dZkO4O5jQd<_%1on1 zR#4H>Gx+@tiX-aCbs%PhjlO8w%Mglf{=c|C?9X$Q>UuFFE>NbX{8{#iLnV2k;F2&C*QvsyRzH>O&wJKn-3$t8{$`9uD4X)xDI$c!k^9FU7BM^wVt~v?%Re z4OsrmZJ)m~6+bX92{t$Nu!dKYDUFAz6bvScEgxrrti0<#(&EfWKqd%fR%qzEV2;Z_ zYQ6ioS12Y75%=E-#HNk+quq+QZCyLW_!w%DCM@}P`8FjPh=LgT_yuLBKL6MHVp!5x zNJp4~cR0?zF!iv|LDm{hCWS}+#T`3OKThAsJUE`k4Acv({!N&ZJ1V0Z#ZKpZ33PAo zKXxtY`OT)=1R&YoPLni%z<6Z`cy>tupPi*fs&X1}8;YK~AtB>vvgtQl`nD8*1`mdh z83c_YtNgFPE0TInF<&OZP>l0IL%k!)l^%aCS(Bt@jUgGKcH4!3PHQ3awRmNG$VpEK zo-n=rST`Lz`Bcf=^@dV~ky;ixjBdqZKe#ciw=u;^2?_+Z-P_>3@j^uw1V_skzoDVJ z9NNA~e7*;E0rI6kY6cG~DQ<4&uxVCdlEZiBQli@(sTiuXL2nYqlLs}ojfg8j8{6j6 zklSp0zG~pB5a)XRYew(DnNOk!z)VF4H zv{ad%7+_N2^ac4go?`|*$k!70Y#tHuQM7y(!XBnkIT1wwWmw8eEZta7k5Cpzoc@i{ zrp0L*#dnPkDWB!4zx^#Y^@iF%7zg*<+?i_zDbeNgWfjQP1Cegx$SwXIEJ5E4NcI#- z2m1xIvEmvTJ0!DZd20q~mRLMD9J#lU7ZX4H)VYA*lkFM9nikjLXkzO_sib7+1__Ig zqsQRSW)SL**BR)v*riub@()&J%CtlhH?lYz6C%6F8{dUb%#_$xlN~!~)d+i^8cKK& zBGs1C=*@Ug+`>V{5n6Fd_bcR{@j4M?$5W7znAU{Ruv4D|ToM6GJHlabFCvvVl0P94 zFc;hj(@;Z|=R#rca&}9amhF~%FxE;Ni^EgRFQTq+ZergEIqp7v(9@67VO4CEX6A}M z%%RxpmnmagBd6o4Fu@`UflkO+k;$losE+NxTHAYDs%ekx+TjdU@Z#NdOF01GQJ7PHgAj&to?b1I`Lq{KbpO zxf>sDDD7~jPT4x!E4E06Ykq4NBfKXFiepF*=bjuVR(_{Mze)=q*p;UOUS|?^!O!)> zM3PLTqA>m_jO z2yCq|ZWU09Sx(!`p*C0~S-XxMEBDV0mB%CF4v2 zuY@=A@b9g;cEZU#QX0oJqnOMCkr`XYydlh2`AeABP(?$f?3G%UWJ^RpYiklq zbJpYcQfW=7Mu{T0167@2T>>XqcjerxhoL9w#}3c zy_>byipYVk8)3JjYx4qj%|^SczDd?6`i2gaio$!CG9-LWbc-DnL|cSPtd@#}>mSDL zBe2pJK~pF=oFTdqB~R^CzOP^GZkI{AX>jqZcJnUq$3iIn4C4VO2pbYJ*jZ6RPX8g| zuB0N6i5Kl-;JF96THQariw0JM|D9^~x3&nlwM1Nn@ zSK#LdE55eNtfwbT{Hym1AHKbJ5`;T;pt+1l`M$+)=4#Op7Vo6W07|s2RVgqhxPU%! zW>HrEg_xoH(y4J8BE@|w;%p!rb&b=1ZzMR>15|l0DjZOpHQ4Oe45gOn=@0q(k6Hd6 zR`!5XQ0%i@gI@@0p7!QsTVeZ;bgCi;=@Fu5aY}4I$eNlUMd$>8R;?kP|E3VeQ`?+B zA_g-~TvPNVtbZgi)t!y?I4LZUER5be!j)sa17jL@*4n6w)xX=BgJ&6@#%^4UBIc)t z#h6dGdyi?i3{7X&dM})h*@ZSF{fE?^&m35KrI-e@HnAiXQzJq`BTZcdV|y7Gy?D!A zT?rim+&Uy-?R$az0h@^TgKH*(4SpGTra%~898b=H#5cXPJhwCn7`Ge7F!+tP`0~O{ zGu(^~({(mx8U_5BGVMrVK@l#isc7Nih)Ek^nWHxfW$r`pra_wV?mPyAD)*9l65Pcq z{)s*)o+Ld$X-9qvNfQK(V(v3n*~N`G?Cn{@yG~B|oD|Do)}%~icEVRCFGy|^F42KO zC<+6IEGOB(pzp27yrf>f5}R0oB+g}o)p*_saFtNnS|#@ax2wK&a*D4I{H@snId1E)|_GR1n z0UUy!3=p$zPvfVOKP%smEjW;?3Zu3r@}xFYZA7n&(lH@dO&m!w{(Z&=x1(J&e$7sw zy5Xuk^ZPM3M9M0f(-+r|c_e;c4U z@|7Z-nXpaYMf6{XA4(DSKX+ z36sCoBqp5++SUf8o%T^5yvI`utM7uE_~t>tR@p%syHaNG(4!nL^^xY5<95?wLq%(n zroH(h=C?Oh0zR4asx^VR!wBC39o}g-X7Tr`^=kt{ra4S6o#T0`Yk z#}OYn%6e8@tvA1i&C|`pl@W|#o7=PDr-#(^ME$BQ^s8=yeB)ciJ!!&qVAPXD7Tic7 zaHgae=X+vgB5v1GPC?uq6JyB5>^o*VkZsgNOxx1lRn78Fidr(INm2_`7<^1VZPjlw z0L3R2T8~zXZSrb0Q9972<*enTy;~wk`{MT)%n}#B>>r5_kgRRSJm}oAxW=Ec0ln8H zUfBYfGRF?|(bP-K6}9J9R{~X%GjNYi2ueh+IzeX802}HUll&R_rH8_M27@_eF)GQ_ zr8;!LW)*Q^&n8w*JkQnlM4@oI4w&o)`->2ODJwz2Z%?1k;6 zt~o5`^dm?+vI$P-zYTT52u{Hb0xTR?%d@G*Bi55K^LLV-WWNb3`nvUD@B-3OxWj-U z^BBTKo3Zbu1?>F3iF2H(=#@HXE&`5cKpfJMA*5Y7n|hagx`S-P@@;w)%a(eyxOPl3 z+)$$sgs^&a+>)gZ7po^&(0|_}XTGY89^ck{JUT%FM^A=A#zPlIyv;AB0#+Go1?zbM z8h!4?enUuRx@LLz1M-Y*3MLhWsTE`2&%7YkWWwf8Q?ptzwP62&ZSCVgOn=}GC7)Tl ztMh1rj( z$MvN84BahO5w~MF(YkWX|C8UMP;vmL@GE(B@!>KlBpAW?SZ>$myDy!sQrdL7le7P9 zEL**9PL+zqoan4~%}bln02frn>u;n@QxbT#tczC1Fj!pcYPntfY;N7~ffN`JlA()< zPc7bSt-q@RxySVYA6))%{3`xvoVuZ(lyn`xP&a|a87>)X$I)E2AH-AEGQcQ=cH8D= zA$@v>98G;jB&_7v_7O+&y%{xVi4ZcFS3JUDSPE9w%W_`1BsOTY`rM2hNDy=UjIFc0 zvJ||Zgq4-x-Xzh=J}$zQ&-f6oxjsa*;nB*f^%~a{(H#V43#;OWYX)`)!=PXP+Y@|z= z!pZyO7cj0fcr-r!2R|6W!&P9k836X*YiITmGj#{wU_&V-LFgI$Vb24QooKgpy2ZAtfY`8?%FS%H z_X>)dC8xsmx}7n_kTnY>KU}E3@#E~)$s2!Teu$>9uJu_mA^jRR8s6*w>|*in%57j3 zmm4gNup_>$yB&*9&=v7iaRMHR{|67j_yUMP3mzLJK)ueY4SXvm1ESmDvh{SUe@3;E zq}rF(wXv~ro|3E@uM+StP-1=L6q?MQ9UB1cF~m32{~F%WOs$Y&4KIAWoD?F78nhf0 z+!FgGqT&m8G9{ErY=HH2NNZzT;A4>-o*pYtD*gT#$qhJOQd${f?{qi?`+zMuFcY$` z;5Zy@Mf4QEbUpQIH;>ukm3GBZA{?5uX~$H=?HAA?!t}u()fAJh|)q` zbyaqVX9fGd3;mR`0Ttn1rPBbE#4uBxB7UqnvO}QNuAMG#lHv)hT`WIg;;bY=a%P4= z^Nq~QGxkSW0v9(1g;S#`i07!%)N|k9>q@rdv~?if6|k zs9e0Bb-CxW9*4d6UJbZ@j;QDN3V9#n>}hx}+LuMCTG>-c)5f9GLB6X_eyGJfATREy z7?iQU9`A0wt0Qw#tSpL4Zp48gc6rX#JdK*6YP6j>{QUP?ZbD9(n*)DmMlhI_U~)1q zx&XWf1H{jfj&;>&Z|*Xrv@7{PQF?#xv~^mDM0IhL%7_3h_Zp%iC5Fog>i-hYZqOym z?$Xsrh=&HIKC+qMif=7k^_l=Nk8r<^^gSHn=h$uH(T>B zA7Q|qkM^g0Rgs6A#4W~M0L#$Ph_&cyms`f_APno$#)$s>H zzA5|*?CTeMa&5k50MVoyuLR|P&d%G_Jib>6x}kNI;(#Y zak}e5gwnUf{TA@y{$=1}Wv5`IFeY*Vq;lwQB0^1@-;{{%wdj|Z+2f|WJXn70 zYEXS@9DZ?eI0xu-7mNq8J@qqW+PzHUz~_#A3MBnsQ_5Uhlwp#@%$=toeC?+ zZ+Y^Oh$H;QmBtABaV?`K!QWP-mTUV)p=Qq6tP#3CER>^>9$p7t&@#8|QLy;(2v(KM zOrlja8tp_bcWNj|3;ApGpVj&gy|T?u3pmiOG`v4<_#~yXHE09Y3@GjQ?%95iKd44F zasc6(#M^vrA}k$Nu{v*QE_j?$7wfAvD#~25(>S@`M+;zHsMZ9;V{NJpV5;cbN zGD_II531fhc1&QztY9Fm$BI;6G-Mc~7J!xI6OI2A{Iu%}u%77)C8m$FZHp35xwSW9 z5m&1sl+xkEUDxe}b;L1#C6S`EGTY6@uyY!r{K}2=MEel3w|JZLW-v!NQ*#V|yZ@By zMjZ3^OLEteRH+L+fpkip{kqO9kSm?{#K$=r^d!*rq~(I|5+@x2A0P|m{j z=kwUn2@85xhGidu8h+s)(Z#)rc5SC{*B1i%E$0Fn*#{ym|4L^$&S`v?uQKD((#^oy8Lu*rY6#s2h4ceCc?oGEFKj>u@=si)ZezxY&QYzLGqt&J-YHGx0p(e%fBRw`-bt8B8G8Z z@I+tM5V(x&eGYHgal(20G$s;+na~uHIox^es2toLxOFuPMH7UiSI&ZFQ0uK75mdpv^u^)^{U)l_+v@QxTJB&1RN5XqgCpGH`%aE8;sqigw8MM+naz9f z(U;aYL-ef3%L0Wk&8XTQ{FhXs688q_k;YK<<@yLMhquEfUA|yneI8REYfpo?oqHzV zVi69+4d^73ct#>_qVw|FoKd*HA8i;Bf5T}{V0|f{Y_R79i4KJ2K6kL3ap-}lPnc_D zw1pR-k6eOP&PRH-kFZNBwlG3fH)?B*V+srFZ!V>*DZH-4$Kmzg#%-!lcCi)*6~RX` zVL>{;eWro@z1?$7*@w}cK63C!o`_A1cdm#dS&m%W%A;Fhfc>#Aqp{x2S?*T&ZI10@2a9VO2tAmQ>ra1NA7Bb z*XU5o&(~LW*lmI7?I6MJ+Wj2DWSZ&$raQ73lgw+RX1qy9jMnitk`wK;rKF@IW2HNV z)|^#25OZkn!Xp`UadFCbRv<}lPb1QoARKzD%s9K|lpAz|7@m>T>}qM_!I1aR$Ww;{`zWZ)`B)Gq(tMoKuK$Rrq7S)@VYbhFwim74H{Fghriot z{re*83ft}bi1$sb^@I0xI@A<~_!bGp$M2ekB;clkni$k%)h|dRNG=|#g*pXY7t{)J%ljQO_v(t?O#=;=IMu8ND7C;1lh9g0Wmhc?)k}-3wtkj zyE>blrlp}e?~#Myvj+E3~PG?L>3qILrtr0$AEW-4bY)^(cy5sJ+Rv2jXy znE)ZM7_d1xS+z=I>pKa0|pBz=Ott;_Sv*UxX;>0-qDYcWbZKg1ANOikY=3C7x zaE#25d*;LV?)cx*owVcj`bcC;tT~*CI=P1t)eG-y6}qwDimX}(po{HU_2*%AjZn$r48Th}yP;&BBYG>%R~D-* zy7~Qcem~aJeNRJ1HmX!yxH$ZIf1XlZNoCdkm8L?z(BY$Nz28Z7;+`GlprVh(YLH+H z8D2VBYMYpF_5VT8Y#S=gtPJuE%F~}(w)NJ{)At_1w68beHOFf{SD>bryl)%ooC`~< zq57>byG|d@CYAmB{rLsO9nL%|6S}ClJ##d<_g+H&IGIY~J$76{tV8+w3r67EZY?3gsv7_0?L9 zgX#WhQ#!+|vM^bm4~Lc#~EP)%P|Nj&fQEQ)Ltc{x?=Scf^e7 zOl+>fd}-q=C7lzh#WBS!-;Tk?^2AZT$~F`=qX~)_j`tK%_q>YJib1&;b3Du;qqou% z0gzu&x>Gb< z*ZwN;yff5@CtUhs6$=+ztC#_5$@41Z+9of^g840He^`B(djqvltG;$T_gVo&~83bgQq^ z4btAZ%6w!t_;szD#)I4Gr_Wttj{j-uw}YGotMM54#UND)U54HDPC(HAt$sNCTg)wj zM;zV~%`NETY<1?M=P;s{YvJ7d*9-Y*(J{EMC+br9}XLLQW3y4&$&wNT7X ziLOr?K;dfSMY3r*RlsUOJKHONPwKr3euY3Nqd-Pz*|T=;UUn!y%nU-#uM4cz{qB)^ zJ4YJ0W^Pc`dcU#e#t>JcFYH@4{eUnP{ZUggDaD`C2*5w0+N!_eh*g}eO6kgFF!z_0 zh%p+jy~I*lZOT|iT@w5RLPY~zgp52qoweFmwqDW{LL}C}rnVHSq~~Jg!P{T+A5AAJ z)})C#r_@9<-(e(~&;u7qowX4L_a79AQW9lV4Q0Wg2YO<-3CB$@Ma_s6P^-!yONoj& zH2zH|MGZ;>e_&wj8TeKAIbd5&V<^PhDMMXIA~wjCHIsGliOT1u1XqtdUuaVdmnptD zL>I}?!TN40D+AYL6(#@MOIn{Qv8M6o%n(naPon_vk+wBp{x**e7Mgd;A1=T70)4=L z%SAwsD4L&p!0*PDm!p42^-dQGq&9bCrGqP3YZb)H(8}AFFeq-$er@f{|H4aMq4LhV zJB|IMNJagoX=Vj1(nf}rkGF35FfB--<;e2Oq zZLe~TFBAZWr5DIHaerMMe8Z#UTy-+kea0=7z0*)#67BdUfQqG4tpkqq$o^(*3K%Sy;>{n_)7}BgHV>?^2bypGAjLY() zR)~j))4G#IeB|b1eM8AFV_L~HG4||B415vxjd*b|s)gv(=s&-cyb^2GWu;l2)kLOP z73WfW1Af^$lgySj@>6{b1L?D&QRf*|KJiw0wFjl;Q9t3?Z^zH~TUej_} zC9|p<>0V7H>jE!J`t5lE zTHFqT!}sD3DsI@!2mz|xTe)+Q8*7^!Dz1zFz%38AkDrmfVGXtCZz*-jcC8eHPS|_9 z!th92EQqs@XHvtDI7(P5BKg!dgrG%5I`OA?cFih;ubyimsy_G|2)00_NL;zg4I}tB zvHd_-&^XnWRLnYc63Y>oyhbX^bzH8X#$81zX{(q5ka#{ z>ZQK{$`l5+zfrsdTFy1oH0%0JZ~)hPQLc}4VN%1Ok(O+ezjoKUiv+mju*B?YL=yC^$FPo7qU4`Hu?vKBg5WY`Qe$sK?gPj?$Nb zQ>+=j>s0g7wT-Ugws!u8u~ZZw8E`0@k%4Z$)UiLt!@p$BJvyilUD&I^wo73k%n^oW zfF+LFRclO`L}m)+g>xvrdLGu(HJI0$lUR=@rc==a5!JH;AsYqR5?MRh5Tv5jI38_Q z>VT4uE6~XcqC=MIucK&y%b)T^0c}&*=b>`kA1qqbt*s>B)+q8kNk}xX4JDU7j?pyJ6IXK zDKijroRao?#uz+h%Xw%pF0&~4O=UFEMAU=f;pis0@5N76~VQ@vlC!+m$k*(Va*?LD0TD>EE zi8}V7xGLKFhz&iO{E2)_C4)!rx`~6TT_lj8|ge4BA36d z$t1W$m29qrAmwCFb|lR(3*`?B1R>*lT|RH%Q1ewitX&hwbF^I0MF%;M9JycCK!9#) z20zN|rjqpY)e_59dze4oIR<3#B+2VfWesCrfp#h8qAT7`I}>ceeMl=)!mw3jrW)!- zNLn1n3^0SZT`LzMA4wYp8duq&_*dt!j!%Zs?G`q`an8-thZ8D8==BNE(X(h+W@uyO3^s_AyF!QRm8-{j`X7AbQgXCZtxIrB!q$Nj4S}a=Rhv&>YW59=xU34 zns?5sv~xj?$G%eXjI2X4p#2X-R{z7@JH_hOuWy=b+O}=mwr$(CZLT$K+qP}n zo@?4h&u{PQq&l@b|8y#yO4Y%5-((z&vn%)gyE4Y}l*)^v+CW{}bfEgh8v4*~e`ld& zdU5f+>&RtSD!v_Tb%IE5*RLu-ZRqc718z4@)GiRZ;E5c-FFIdOtCg_WuVwx&o6gFAY9eHiJ||5Y z`>j9h1IqA8Mw1z8$t;1%QT;26a(?ZxeS2DTt=Z1H35)2jHmLwi^J@k4fw+hNhXfzk zj(Hu-JzY}h{gwGqvQK^`{@tyW{j%#}`u$FQp9_dvmiUGko3Au*L=3QnBa2JT5fwr; zDc0#zRCr|YN_|WZN_iRZ3=>>8&+Skr_!f@I+POGJwDnPmfD2|~|Mt7!OvBmcPJXie zYjFs>0-v8`tqSgqIRuY9PX_>j8O|SFOcGXIhavr8q#-<8quwA2{X#-4uy|iX$9b>N z!1ioE+Ck-6_`~;CeZ0UwJctv!xCj39#hR6b1Q;BDkk3gJA0ldyC`-qeY-jBo#JT0q z+!wR%Ub2rIaN*ka_HD8DAE_5GO&|$$Vy)d$lMYU~)}aap5@Op6b>^C5>qGMrb}X6F zm%mf3If4>vBS(X9k;RwY@&dm5-q^*gT9~j?0v6U1MYo=$Hp<6FFEEl*uMi%)h1E0a ziW;|#wV3h^<`6bC(+IghWLSkLR0gb1!Ud@5EUL+X4|r#3+sKVNtMPpHM}XaVe{KV0r#o1)O({9 zxsnwQj+mmS;wCF8HBQf#N37N#rdTE&G|%vvI{7#@=%h+Tscgi9Cck`pbSIV9#7%Q1Snb zUg-7LQMwakd@0>;@Yr^|8)sTaE~x2jZ+c+>)FRwop$wx|OCu4_xx9TVe#=xft?apc zRVAu?RJ05fcxt{QZsx5mK?x(u8Jcb60}dHg!Fy7tQu8kN#E&goudVXeA~PdYh`R?T z8!w2vxxny_rpEJ5l2%^X2&i^?OBO(DuG~I%M3&HG`k2Um;Gxm;>Fmv zE`lbsa-=WsM@kpbHApg#2OL#sa-YmJ{sE89WU7tFhH zMU0&IYU{1I&%-r86KlU%mOa)C#0t1`7NXZ^X3b&wGzCgvn>04VU_#a7KqGNcaAxeY zh>5#_0j$9TS-r$p&<&5(Q~)tD9wN7@Vf%X*qzyi@TVTklpMm1W!m#roz)`$wuJ1$BqMCH`ofXi_7#3`ZILahN6GI8!-Q|~Y2n?NUwYP; zKg+XlLSN7xo0T>2lnK7MSi^+cTFnat2Edykptp ze*)GGlPzVq2z%t|Kglg<2B(>K`Q+*Ju)7w$dWrZo49ipb0G#UE4#*dQ6A@?;j7EIG zZTzY7K@dMek3eOYK!jRwW}i~d2VMe8rkqq#U#85RO$|Wt#TB(?B4x}P{RGCrPAqA0 zjhCbi?*1#a{pZD?yDPfU(rQ z{<0e>DacD(a&lLGSV`!1yU z+)>gw^It0dFXS5XcGX}r;X#6-VQ3*LxeMx(v`MLKqCj3T$|~b94H3#$vuq*leFj0O z9Y?3>UQ>E&xT*Q5dY4jGlnxo5KErt%n#U zMW?#|E{TB(aYSorg&nIY}!OT{e02P@(AVbLymoDYfh&i53O! zvH7(oa&Y%J;dG-!|X5b9Ykq$!i_&&@mtAB0O+cuEb zrB$dL@tx&nH@~%hRCZAe#b6>vhGRR2;nO9Nvqp~VGIY5luQVXasor*AX^896N?2NI>mL*!&2$4VAN++K00(M+i1$J@4#1%5RU-tFU?? z9SudWy=Kf({wpao4~|(*42K59*HnSlmcnjd zkMKdiA!z@$Wz6hI3K7RVJZ`I*@zpv-X3ldRvUi=6yb^uGwM`tfYwU!!Mds2Y9hHj;W0h64{9VodHhiE)CV;Tua5>yuM zIFaz_QGm9NvD{l^lR*5)<1{YwlZZXflHgu{lsD=%q#t-~V;SSrg zXL8%=AcTwY^{$@JV+(Lx25+t@!Y)k*-{ao&rV7a0PCZ;hSAl-W~np>k7`t2NqBmvmy(DGm?LWIRc3UW)&x!E8;n$6Hzy+syYX8(Y5sq|$*=V%50fYV|;% zC7u&>eWKzot_kgw$w`mkipBxbsp=;Qv*{6zmjL*`eVhv>wzPbttm)({I41p2;Db!` zoSlV5OqpoCDtsuTvo!WTK;DCs@~&$x`6}4d`iZoi*>R4>>P{5Vo5lCRG7;qOfvf?U z6d_b*e4mB{sU;tgv7H4Z24?bHpd6vS!5uRd}M#xT_o|WQP*cLhRy?!0L;+e+B zJxLPc3pmxdyJTRRBO2Q^m8?&=Sp3Wrx0D!fa5OT`x%sg1V;CC2!$rnw6zb7fTOtTq z;9u{M!UdnSj30RN6epn|ijazm(zp`&b4lGp5Z51rptgS4A1%Y81@{`4y)CHCLH|A5 z;UnHWPEk_Jf%x*OG>hh|9CPg!w$?O=5n$6${yW(gGv%1fkjb}O*?QW`n^uq`k_x0S zO2eR(#stH=!7i6;+h)KqgCBK8i@9k9nCf41tHI{N9N$pYUlCYB$?>B5@Dw3(s<>R0-( z@5gF3qGt*4Hya=kXUNOTe2$y9;wb54m-#N1gJ1oXvV46!gTpdmo_K0jS=nMYr7jo}^bnc#ryro=B|y8bul;H z6Kk2}Jr=w9Zgh}^N%{o|Kh4?HHnncO(JZ_A^Rv;~mcX{rY_rk4)kXJw`4wS@YuY{b zp8uLR@F>3Q%y5*k=v;+(2kn@>CLT@o(33c+8(eXlTqZSL+q9eoeMptYOQj-$Ue#;y z7F)A9YB}hgb0XBw+eF5mhh!gZqT*NciJyy7CI>_;g)Q;@5WHL<$ynb&z`5GYUJu7h zM0I9Q$R99_2{*0FmGTWp*896t13bmHoQujAz?#qeuW|iDBa3hjFG*V*e0)1T830hC z0!|H6s;Y32*o8r_H=U+%Sn}s7RBra;OdM$QvEkzkja^;eF~n#htjMwP<-SbSH>oe% z$ovcP1zgLZxnvP^Dc-E%3=(YwMn_(fpIK#Q9)PFm8LBXn&_O%nk%daoLEmx; zM83!uB_;y>pZPtxT9=&STdx8b?>J4Z$c9IH{2vdJ5 z3pb@w$DdAa=IPbRB(527B>9qxZzcGUN*k4+)|}4pyF+K%4bkr;PAE8;00&-f zmX3UN&otL@j1loM?gO1~DzaLR2n${_%^A_^2|#=4RolmjopovSjBv(IbgLNc5S`OM zb{4N^%CsdD{mX@eZV`|lWcHrjjINO2#w}l z#JWv9o)~{o#C(oYDA6OauI}^$aSx})RxIagTKa3J z>kmf?Y2~7yU5{Zsv7_BrEX3&MG3T6U7tIfO2IFKVJsy<`xi$IYlrm~kGrN8AF z*FMzs8BzinHD@c4cH2lrVQSU&8??rHkE##=$Jgo-WwVawsoy-or6P;m`t=6rChE52 z^0f*}2cMAx8B9h2aUUj8u{R2BlwXtg9N>$W0zx;ykMV|QYU6i?g|boj5z!UfGR|)j z5v82x^r}n;3&7z=Y^L*VuXu6_S|R1j2VtH_Z&s`*eO>y4#4o}t;NvT=QwzMTJhg1< z9GMbDO=7Vl;e(}BoYgC-K)ml*-`e`paAhxWkG=B@X4mAGUmD?8e8OGGDLJyLtiYd+ zUzxQ$6X@$9`m#!}=3nhSyiAxG*LfdYw8?PzaXOCDlzciR&qJ!#)&u5_hR9?bxXOP^ zqjG^njW;CCpO_gu#ARq98K+GEB|AJmWasG^Q=R z$hFB3Wyrb_#R!=kyN|1RLE=($(=AnH+}T*owWg=j#{LBqURg9QG>KY3PRQ@7w|)S^ z(|*KVgG4!;SCixmlQ{>YqgdzXi4K6EHxLTsUS*NG(p4wIj>}YVl$pB*uZO8b(RUga zA6A5n6H}YiY7bVtB1nDNQ2Z5}yF=N>>6n~O65hwMoWK4lUCP$Vba-FQ6{ zFs$vRH|ef89btVh9+9?$KA;WbvM~>#XUT0vT@WMO&SslwVq*G*z6GT()Q_|PLVi|p zpTGgss5GN-JxY;}bDZ{UM2b!DI+2okZ3g@dpCc6t$QoagysK`SySabYV4_ zbutVYwN6@U*~pYO_u3W}@uLUi172+m28l&~YWM#78Mg!Y(QbT-v*)HWdBDz&vAJOH zo?Oz_o3R4$={)gA-CQY@+wx~7jGXJ~s)kZ|n|jgDa^H0g76);pE;37dVB38uGnS*SwA%*bBQ33Tn}-dy-)N|uov|Sgrm*e-_Y285CFgeHGJ?tF#Y5j4 zrrRV8C>pa6-oV8-c#RlAxGKzn!|Coy4oyTEE{ab!cHou&>ZlhoX_h*THqpu# zHJ?91QUiLKnv1RTV=M-^htVHU+!SohD$*7T12Bg?8Sob=Wbn5V!6?mg=(?EYJmN31 zTrqK1Y*v#iP zU||cSFiaS}-1h1@QLf<;D$9Uyh#KuHShd+>Nm)<`hLP2Vb9a|-HX={fRO9Q7_wO>ODRH)nO(yQpvv z$!z1)=Qf2E8mxQ^u*MH_s6OAa+!2-`aPG@u9I&_&I8OGKt1PCk=gcx&;L!pn0hx+s z!kUhAD&$6NMhNEj`3PQQODG2{b6KpD`zdfxIF~KmsCSmk#|kBy@z1M8kG+U&_piop ze3bA37n)Ow3>Cf1c&EWw&DEJTAg9;KPF{2c0tdqTy2w|AD=EB7$P)zAh8N?KStxDb zqH7{Ds))80s23lcXJytbDKJJH@4vw>lyk3W5D{5zO#lU$BnTeBC(ogbBb+uA1e?pb z&RmLtDXQ@BX$M{{1|+pca3r=pE)lIJpxr{V2>1eY`O$+gxiT!_`1*yUY+~WM;6i^T zoj9o$YNkBQ>E_J9#c2Rmo}JbRpFDF(2_jug96- z+)e_fe9ep{VALGzF<3`+*woC`iTHk!pMYT^^jg^p<;m;lLYa|)%c=~ruY2a96{o2v zNaMM-UveYk%|$iv`BV|>hrOw!9b(SMX1=+8O|%T3-O}>UkRa)}x6Y5yESylf;$Xr! z;z?CU0@aq72HJbyn*lqPtk-m9Ej;K7s#6P1GzmnxZ(L8mot7`$$4?3kzfZ53HMA#j zyCn?Tz85x%E2RyrvbxbEc~e<|Df@OEXyQbC4hs8n3dd)y$3Mzj;D8vBX$087&MG&3 zerBoyH%mL6#VN|k`BFFhlEn3usSjV*v9$B3RN1mj3|1l}HZt>~kp#db*t_#8{!NGO zATP!GDIF|Z)-LHPkn9Fnp;p-v(mK#6v*`(m?{uoHyt3%y+KzF8jJD7lLPB zK?}zE%Z5UHC{51QMIP1#B`wOy7Okf&-;xDKSU}AFAp+R3Z|S6{B_na~3>4IN=z7nHQq-jBoJ@g7Rr=jzZ+ZQcuZH;Hq;rV6VVXh%JG#2aq zBN5VNFI0AqrF99q(>5&-Xvjf^T+v)3af& z^J?zWQG0kd%k-F4fp1MEfKwEXbb^+LBI^Af$$&=flw!sMyXcc{MtE_-x?EY^{WvAS8r_TGa(UBv5F3)3*KorgJ?-8B@lfExxi`a(+qA~zjT}TJiz;+ zZ`X!|hAt9k_p|$ri@cNAypFk2UB`4_XN^Vpkvz(~PYkyypeE}vc zqm)`orYpzCtJv|_ov+DdK00cGjgbJMC<6XG8)~Q;IeF=?K(Vh3&_48u~prm6tpynCB;j-Xpm>Mj;f1>4+&dr>Z{iuMlEUg%pX8DJ>K_ zuekP8l)X?EwcHI^GeQJN_JJ(F|w^8Z3*&nJ}U5q;+VV{#Rz1R)b&7$<@~3 zmw)PEA53uVbR;Lenc!BT3%8Ul(APg z?F>`2;zhh4T4NeK>E`F8nQ-4}4@5NnJbdtv-?N&)4F7iSX`_oO(fEas%oVZM7%dvU zA#Loq6n9?+4Ver?EguhDAOTiF0wHa}dS+j!)JFe#eKu}1I==kI){Js2j7-L2~49j8!N@`ZJ2L=PCak%M}JuhDLJu=pE^N<#6M zy;RE*C(@!0g&C}|5rF|nx?8a21^bRR@D=>*vmo%_MZE?0BJ7nACif*YjUmI!^CU{*z(9qpeOA)@va`DB!cXp4`Uc z$Gl66pxArb^~J@)F(u+Ay(C8QJ93V25TUgi(~K(R2A~9kb*S* z@i2;|p?x6WA5j+;td4&qV2~I}qreS!u3`qkB;If+CibAf5QLjjo!aV8VDXg;hTrBG z?^oB6C3&cp3qopP)ByAUMyYiZ|0OyQpwTP0ifFE=J&FNj%%lH)(53ALs5)a1F8Z4U zlSKf7`}p<`d?atqvel4NxzRuX-Gv5wg)bthOsKN#)MnlT!tfj&xXiVkapO^bS%Y*G zK{V7f%_it?_}bDP9WG;Q@l94o#Zmrqs^_e4AH2PQf|kdOTo-57N1JxB+v3{{$LY3% zDc|c;%dQkB%RZN_fuDC~RuP0qZp~-gllAhkQoDFEj5Z20K_donK4P5f@g6O+$y+lE zg;bnWG)`{S?D(vQ1}i*W-)NS9g{;SAfl^Xc<ZZ)==GiNJr!Y z%Mmk*J?71u@WcGzJYjKE99r8@H-WXg;ejoH$Ln{-Us8QmY?fk<#&$J5cRQ|EW}xib zAkAzLV(t_bSseSIz|@$H3X|OVbaB5YlAb73n*0OBb!)WQl9}8$cu;%?{GI4Hi9ODl zP0*o7)~U_hRU2ECI$%o7E&}K<6=OC3NEB%wNZ1sK^*D?ni|@|}=5WK7{(eD#`Q?kE zwA+KhNC@^am0rhm_gG+e6TAkstAOMM`AK@wT zIA=@d6A!XpSUL%i<5Y$5%<5%GK|yM)&C!AimkYnK>)@6cOnn%vqx)pPW82uipp6$T zkTxV>pJ*6SYd!bPqxcK}VGAxA9*%vbRDDP9`a1D?&RN_4I5^`InEcvWv3zFo7X%!S z&1Ny0Z*;T3SPT*#WGnh|mU8Irp1S$qnmJGd`-wV48`wV2{jcvQO~Sg<>GV;(gVIz= zX?At%mwu2?>7(I3aN)$>Q^WEUS=lTa?{9Orz3Es$Sh&%0T?KfmIfQwfEwg$L3n!o~ zs)nC!&5w3TDC&FHoX-*`sWKkE=U+IT-IN3^dQCXk7N9JR{RJg*+9}HQL4OTYk7LXN zwXCZr(EB%+B@VF94X5t~N;Me)6hKUfFBBqm=oO*uZb!sZn0!z%wRM#U1t?QnyDnbv zFU6e;Tb!~TF~ZFWrL=l%2x5;HCc&HHoupFK4zDl+e)WT!f86Vrk+do=2CP!%2+NT=UiioNRE_X;{b1NL7Eb!MBDp;YIgw08lR-PRm z&zY*DzY1|DgrA3s9^YlJC88*zhm6p$Tt=eCB}+$+>cW-wWxxeQ7hLQJSsA!{aXd4$nh4!$i) zJkId%W9lAI7kvh5DA*gkN-Z;K@ia+8=~Jm?ZCEP6=Q09a>*sAiKWR?M2GXR`Mrpbm zKCm{`_`m-`RpT$6e<|p%e+7-R2{L>8jb|OrJT(C!{M-OXFqIvO3!2z4a+W-!iQobK zt~r~cdJGV4wukX|@nfdPh0jOUqk>pxfoN}Qmc7eZ&!uvBWT7C2FjbD%ndSYFFB%i?8S-->CAuAO|d9|ytKdbR|iI( z!sBVT)z3F~EZIzocr{-;{`jlmlGYbXoL}~Lom^Zc3IY7WD)iV=M0@$heC& zjbU{RsaO7R@>V$=W;1pV3F_RsYzlooq=udeT7I-n)SrKQp62EBdy=81u{5~GON)k( z-)%Aomh@wWo{tzYoJ793F9gp%d7`gJyif)d4VaG+cT$O#QKZD?GxP;#zY?vo*4$qB zTLRjng-n-IcmMq6T=Q^NIG$RM1Gsl6Iwln{9N~K{a~4KIKd?nS-6q0d4Yl_*|5PCx zz48dRJw3^{X^umpoKOB{4C85A35|RLcByfGA}cQpiidVQeFV|W=1c3;!OhkqFlD3v zJ|3tJ8E!2*pZgP-w#+O)#9;i}RrNR7qOsarZKrEenG$ z2h09_8N?O0TRx)b4W!qo(uj2rdu08BI(E_Up=jQcHU3@zDf6rEm@NI_w-lU0g}c@0POdZj|6TAe2X)D_)Z4df456;uF` zZNGU9BrL=H`KCp`gT-!?C-F*?ce~=?S%+8W@a9dmbFlC)Zgmgg#&^c@u;?GI9+ZW&O;?`>pd@N$UA54 z6*cVL-5yF(Cc$Ni;uf3M)CDC!?Zqm;q;u!D1;~fSeK^{iC}_KAD0;Bga3Dlo1hfp%0BW9M z8DktQKD)orN|%$Ts0*xQ5{j;0zVcIRGp=P{ZEfXzq_JU8nODDHK}nibhVOmE z@?&@jROuHi*Pn^>#66jf0wFHl9q$@jt=3QaWSC{nSDsW^U?!2~g{-L$8{8Wg>D{7F ziTG!>bL18~#HDg&Y%ZWQJ%d+S8!9DZu)z0hBF$}iEc0izy4lJJ-YUCuGDs=n zvKsCua#W4uJr=X#V=D3VuXMjW?{`hXyiWgQ;`uk1-bl#ZeZdlG4Y_zdRL}=KN3+ZO z8h`fL0|Bh2Raer9$3nAE4Q`fb^S3!)7W6r)V(VSLI)f&#<=SDNp8;V!e?sz;STtLK zOhu;UrZ5M>^U^bwyVOs-rY{NS8lS4-%25ZGCC22g7Nr;o5#r@elm82rjDl6sg{rcp3l_#NN zeKieP!g zJgR;@JywuC-a4KKLh#XPKA=PMk(_|Vz6*D*&FioZ%U4!q#AJ^SxPKUwkyO!~4}+gk zM?^2^6$fs7U&11rz_1GbGguh=fSUJj$29DfU!b|LP86bAx6?A{OP0$-Sz;U!<1!Sm za)#VIV;ux!EcFLh+^~}21Rn(oT@M)4o@3X7F)S1O(@mN>i`0}2QmVyx*G0mnUuJwU zi?8wTBEsCcVPCCq;T{baL0{nwE{cZ8%$?7=>`tS0wFu?N4IsCL`Iz0D^;9HQ$Pb3# zPRN0?2C>C~p>vFzCi^bvKbdfDgSsByW6Apv`M&It#QAzUBCRMAV(>3*fAfp@MC^4B zft@S4s=OmOL>;bnGoN1Va`8ZG*-<8QW3#9(*2}bgPn5^3*rFddI?X)GYH9S$T3XDZ zejk>};JV-?_aPSc%Z+Q$ghCFm;}y>i1~9~1fSc!u(~V;~b?n0|t7WnbJwCvFQ+2$I zAyShgDIDHl_TAqL8PmPDkX@evx*9PIbu^zu%_L&P3!l2J9LGcM%MC?Yj3fiLIi#BI z(@zu~lx8<9qebw&oFVRsP}y35VYKF+#fBKuZdE>JOWXLp$!v%u)|f|ie{g-cRs5L- zq==X)m`Uc+V)KGv)bDnB+J`M{oAARB_B-REma*h?qJehzj8uIoNfF1CMr1gc;SCXp z57)sXoMCU!>F;O3&n)YOqE5tIkYuE`y+a(Ki7#D*6OfA9!9!|`JPejWVNR(ODfONi z#yOX@8tU`?V##F(;u($l4VO&9mcEySyTAGqGwyb;-VLMQ4Km*;~|YVKHJ z(rgDaZAAll{{1UP^w9i2ZIxj9AGS*1v$JsgYgYt5{(rqK!bU~LZl4~$>qN~RE@>TT zNOn{$BtA1(9NSeZ$+AgJTpA4m5t)De>-{k!t+7J|F5u)g<2D0rr2F-$F?+Rnr(MFi zW~qVec_q&w;qLtyd2>_Q$BC!?>TaNIx#{5IRe%S^r@+=-vIb&YY^kI)zcI&_{rde%AkC|) z368p>`6BN05L;R#CO!7e^}QNL_|LkazP{knRYp|wX$yy^RD7o|m)@qs(&d{zuiAnc zpGP;eUEv*VDlcpm*3(u+`)A?QOjS+ELZ3j`e4k;SX3ITE7Z@L_SV^&7%d4!X2Nws* z{Az7|vE9~dqs31 z(fs`ZqhNwS(f-T0@BGIO+D`cQ0l?9Tl;^P7pQxM25?(UD4&^vR3C z#l48pm?BoPWeSa=k~)sRC|p8*t=ynr#`zpK)LkDxo5DwRvLi2t+A{!&9&l14?S|Wf zAU#C0qfVTr9-!FwuL722W&oODlle}!vD6;1XD)-(NM-&x0I?s^Ehvs)M{^j!gzTqq zVD7?yVhJP*Jr|qRZ}t7N+t>PmzK~JPJFIN>3g>sY_&w4KwWq@lWkFX93uK#*QIMnT z3dB7=YIRWcxJn9mYzw3YcdvU^gQ^CUn0W^_8H;V0Nu$tQ5laV{4z z^Mmi@{m-61`pZ0=-lDBK~$L(n@k zThO~$`xc`MZJ+6O$ZX%tn-M;_0lW)&pU;zG(C=bE@S`OCqe{JeY)3-CWzf%_QkN& z;mwV4q()-0;5=SF>2QS;H-+@}2A6NKp<&F~IXxufdwjZfw;q&lJob547ti?Kd1Gvg zAL-EhU~95nbf~$!OJw9|>B*G@1;G{t4GA{co=ADZ`C7-y%JkUYXFc4xI#L$a8JNiI zwBK7Sv>GgQwd+?u3QnDSBD7Y2GMf>~u3d><%{#eV`U&D3Md{ndTMI&_A_kxb1Oxqs z3kF38sN#7Fm^^4b=0664K&MbyhCt^)_lWhE=p(O_9Ae>yJw`!CDN)v^E`yK>AVFt| zT+UQ1Fpo)WKm4V51_@BT$FPp~K51sOGj%zoMH2HK)>QQ#?r!`cuxX#^q1%!@HT zwHd-coIWUqf3JpxyvVV> zu=MBwO+Tl9!3w;><9v45zvg=O70G%-sz1%^u<4Xz@T?5#A@kc&|4*#4{3lk~{y*hv z!B4K>{Ub(Vt^~cZm8Ncty@9LqEQ5d6Nmkis~GE-m>GLc*j;V<}*}$&B{^ab)nYpb# zbP%Z1E`5y2WltY)S#&}i!x^0qSNkcg=oP(|olM-ey2a-?*J_q6UwVt|e1AUeE`Ds_ z^0Cuhi=~>Qq*)rbUbP6ysMQKc%x9m z{E-m{P$GmUinqtV3z+>yl-o4I8}EU=f<5Yu4?t8j&Iv#SMl^#!Pk~ONTGkpm(pL{y zu%8GS&BBsLQ43liI+w0wiCpztF$J4nQiq_Ig`Qy9VC;!xtHRkxEEt2BFD)V#+n1R#HH^XvEK#X6AF#s5@gw+ zSBBZ>Az|FsUkt;;rn3nbYxsr}3PVT>l|l5=y3ZX(S{Lh%Pzt@kH<^)CcKB0Bh*WpB z{V+tMH>FY^dP-vq9oGcKs9f$d1Ko?T0z=5`Cr2wY8otm!G9MRk$2I(*d49r_Jmrz> zl*jCo;`S$Dy_eGjou)aJybm&6L=z`CiN;N`LVc;9*7DJ)cnid+ct?8wMP;SQklBUQ zPqOMz@easQ{R6M)wW@chFBPv)2};gf2TIOf21-s@_Y3sr&+TbJ@8R|zLVXE%gY?2@ zW|hKc5`UieEa*+z{UZGlEeG1~h?=f)#U49gtj|3scLYo|?mD?6w(1U;al;?y;=DX4n2X)4d{aXZX%X&d2 za!v^61H>|ZOY}dH%KCo{sWPWKy_kn7SDnv=b|*=2Ymr~kT6X^1}9!fwM6^Y!FRU)BC~6ROoNe@)2Y&Khx> zcSoAU8(xml`7W;Rk-S%$NjY%1#uxgl-=tWz@fz9v{eIb3_S(YZYp=TBeb`#b*1?hDP0_TV|qD1Gv)iJ(U)&r0a&{cKOqPYvjdVDb+EFGvWb@h4Dr4_xKNi z@&H8nt>S#}@7b$4;$QgzMJ1D507YR%vx)Q-=_M-_9bn>uOh813C{WO?ECrSHVMJr{ zSxOekHHnI7Is6mb1^unuqn}E6?}rxM?#;L&MRc(tt%O?De-$|3BtzH>w+2FZ3SdW> zI8WX}vKv?jC{4=*FvTMAoor&RI%3P72duc#E0Z$$BV~OfULLZ6ci+{T>!7-I?n$2G!tCr_qO=-tdQk zdz^AuIp>Xy;YnDPDRlZLMb+8NJ{ev(&P#X_e~|TbT+WfFJ(rvHntoH-|H;>vN~VzG zOqb%9VUF`?@|1tk3iGLc(I7&n>Ma_l>Kz;K(^tc1)F&cmR#Sd*wf%>~993`9*nfB4 zf3TGOVbJXdU^XgVC7)Z2x1TDU74j18^6%~o+5?}HR|20yq4vY}Pqcd9roCX~KnEXD z3;si?F61rVdoBET8R<7wvgMthqCCzmUS{M<AbQMst+YQ;KZ^7$)V?}(fl;hlFN6_PrlT!nrbFfn$H{VU z@`1f3S|_c8WGq@QidRfJ>Av7{W!0HNkHhMLlK+xWsL?GC~os+a)`x~}W z)+66Xgpyy(zsfJde_4J}qgVdl$}h z9&m;GPx*E0dT-5+9YFXwsCKb#<)iZ~_UUnPZ94xaP}%=yLG82sSNTQR7Aks*9}fLh z{QoS!Y>I#g60o2Dl1dvYaJm1IN?~<1zTCWW+H9R~5{?y134E|8b&m;WCvWgNI!i)c z+wWh_*FGm56BoC#Jh4LW7jkFqnoAYFUhNQvDmz!xSv|GgWJzB(8-9wlvF*OMJ!IWk z7f%H+bo?+^6Hkp$nb*ZC;bn9TA{0Fz?whcmS&S{3=kV%JIxH7+0}Sbruq<8V8`kf( z|00zTsls+6+gSM86>pON?*9Gu@VUa5N%+1koi}9cz`~m>gl+D@;Nqk8a%b@o&BWmN z)tmi-TbUe(2DSV?L!SP@w`bdn<+HjEZhYdsuOf9gRHaGiBreS5e8lDZxS(wrip4Hn z=OOdnYv83d3Nte~HgWd)o)eMNsUQ!%KnkdzrDS)ZAF-Ci;%A4V}f5=h*_61Y{g z*Zh0ljG^OU>q{ffPys%ye;uCItk+z7Ltul`|6G?0V%uo->-LTac5GM583 z66Y-k$bU0RWIslU^T#OtT)v$DHcD{%1S$iwE|*U-kn_@Wv}dp5^tCT~ zSJ8JqXy#SAEstxBoqvQ9-H%ZEAIk6P;ZOOEN&lIO{8N4jCA$74fB#i}H?TWN#C?7F zvbGEsu0!?Pl<&#-JUHX73-3r%`J$_^I=*Cd{N|2Nvgt++w*Qg8mPP-Tzt0;%O83_r zANXCJcv+AZ*8dlKZvj=;vbBi@cMa|kG`PFFI|PEeyE}p47D8}$g1fsza0~A4?(jC` zoO{mwZ~FcJ?a}YO?tVQ6)Yz+5?W$U}YR;PVeRJ0SpvG8r_#07WqWj-RR7o$qF-*#|8_t(1Tgb@qm9;ZN#=YuKC*fWfRRH#T zZOM)^+D{Q5ip89EaoEs;D;(|iiv1oYR=)5wE|O6X4_+LNow>_!?REuQvR-Enc+29O z(sbS!A6cCqlNz2fheo)yBYK-(u-_?(U)b+aEkxeda_ud@n=3EJD^Fu&vbbAkxd2#( zHOeO4a$+5up*5Dh5&%3+(-G;>+#lF)p^oXljs3>{!hVZhuwUliV!vs3KhPDIhtdwo z|HOWQ{)YX6{g1$Y*3eRA0~QHXQJwjJTU7r+)BGP%{hx@Yc@@=vqG@dZLDMtXIRQ1z)WB7p+Kkw8o z?d^kTX!bI=4?TGoU6*f!unb)1F z$5pFwOG@)$UVi{<8ThR$Ycm*tr+E+hi>LA8Itq9aNiZ*VgejziHi; z&Yr9xc$dJAUxJlu&B-vw8W$mWqaH52k{_OGmf^<$q8h1t#uk2@efmCeIWh&``LVF! z;XSn!cM5zUD*~p3XyLcL2NosR$7Rbat)bS#Q@`^Euaoz=<+_@Oqqpn26L+=~t#>9b zpi+_%>TS!t^)PYrSp~QKjBjDmNrX2EZYXh}b%yRq$+}7Hy`T zK-j>T80^Z^m>6If!oEKJ?fGRAL(IStv4EvCZ&K%vRTVCTC3!sE#4jdy?Ttbr7ElQi z-5l?=8r868tv5^72b8XiG8X?qFgmOm+iyT*{^s%N=v}rSMDlP-eHnY;*|ryOe%dA9 zfNit1MF2phH~^^hJ|eD-`2|#3eFc@Ewts<2pa4)QjSm1SsRcl+@4bLZPrpDV#=KiB z5ZVCeQ=;7FHV)Xj1fwu7yIHuCzUUd!57=b@M%D5M#+(Jf;oJvj>2`VF2(qaO;!f)=dxCz7zobB?VA5ui&pBtqcJCmGO}I6Z{PZfWI;T z@K*)^{>uCUe*xRmN5G{F836nx1AxC|Jg?v{AUh=o0RH~krh>Pk0Ki|AU*K=fFYq_^ z51K{+K-1*B&@>^hG>zyBO;hkf(+rvCoMdddB(D5H(^zf%ji%ua*RT77rgah@31O`$hGtZdg1v}@9!k_H;zh7jCB9yt*T@6kX0ca;^%9xkor)u-A{UuwbGRS zF;N7DWBAPSQ`F^T6bfZufC^mi-7Lg%@-^S284s_lpGrM7TlsySbcZ#n#aSwwYDU7? zkTvWi{rU~fGyJ=|NDO(e#XV%HNGP=v-4JVG`1b~E$|Z8!J(7s;%fgaeDtn>=_iYY= z`0WKfr9tzua&e1fUSP=^jo&%dX7QoRbnD>#C_O|cRMfcz90u8IeXFDEOLpm~VITw% zBhV1OMH|_Gxu5lAVi=G#=x#1$)@Wv|9o8R~E#>SZ*i((?-C=_d#%CC-s zDM(Ukx1f4};AIN-t|u<$h6+y7_=xQMlA#iZo#rS=A7A_Jh{uC3+v$(sXLqM+XDw~S z<@=jYE=TM{UMIbK)vHeH3Au96Yay=c!8V4uXl!ai+x`FYP#Vm>Sz zx4b>tm}F|$8VnCw!f;o!LR)~7JEr^`XKU&psC;B=&l15}tOb1qm#hWk|GU@!8{Zce zx_{&KD-Bw$@gX+dQyL+NZSciI^biV&{p>*wl+N@~ac92bqgoBLuwOZL{*L_?$`U?4 zF>&u`k7a0Qrq`-sc;7E2-k5^NQdN8IhfL3X<`ZHlJE$4l$!Ef##MM%B}?>Dl2c zJsit*OXUk$nWo~3bCO!Wz30r))R4W8pE&fIYQ4#}E2v@|w7)VOwuX2S5iX-Y&h~rB zL5hZos>`k5&pQcgHbY-h%Ut}B zU_}KzF443ev~(t(D$Nf(2-N^0?0Ag3YQh7C@Y@5BH) z*N-`Eb|TOvM5Onk7O`+<0*h|2Cqk8g&reBl9)Z%+=a!WMQF$TMWI!+9?;KOdB>)|M<3 zKaJchKJ)-nri%kSf?@6Bie?^f$L~)u@`=7%fm1?p*#8D}LODB*P#4b*wy2)wG;zOY zN8WxB!H}?peAQLHwFE|RMyPUlm=LwBBG!<+npE?iv=x%` z4c{`tv$@;AQNA_PS}L*zWg=MKpOE=YxUW5eBvmJ7Y`*j1k&O5a$X?^JtJD~RcBvw}tk{?7`HfEMA`MZ5n;L!WHDX*FNbvS8hF=%(?SV_s%y_PtFZij;U zjA;Lu%*`kV8|u?_Z3*DFo;aqK%t&&zk1F=(vFklfbMY;$<}*1$PyJY(Yg<`{x;p}+ zmfWcA-0dh9VV5SxLw3(uyoCZk>VM9y)H7B~nJ*D%oX?$_b)%%1(fdYhKB98lnZkf$ z#|1anX1WQCP(q23dLW84L3EpH47nf7L`?@05Yu!TLM|AHe+WkS zGS84`0dy(1Sqb*L$gKvbQw`SfHX!|Rg1jriM0&Wz$aGdNRJdnSgRt_XAP6ZoBOah7 z23fa(EQ)8Tvm&{fJav&QXI@=cFBz0M2xUORhTbYQ1=vDkkxrgBEh`saT%O--xk+07Zl3;z^=D=OH&*`t z;GvEm42LQJ4e(H}DVC_7qm-7yhC+_<2YL2-G>TUFUIVeRx#_y)YGh&M#eGa_I@@#` zTpzdA?L3r2{b2Cu?j2!eRgqhl?6Jt}rhRAUG0z>eT|?p2f?m6CSyNp2L=#l&XxkLy zrSzy>b#|=$NWzb2d%yQA6OniH-QXr6`RY*=aAtd-cHt9-0WY~wdbhLsXa{f0;rCoG z#62wu%phsalBBmR6T%biQSks1P+7CakMa~e^2>N;QHYmI=J6WJ_fOr8<}>#bH9SmX zdW5;OtFOzyE4p;r+<2|>BaKyS5)7?#-1+T}u27#zJxUI*e|!df+-W9nkM>*@fW!SgZsL?Unijmf(uH9gb+{!7MeD*;*$d&^atTT?Ta5s zrwh_$6M@Drhi2oqq5z3NgF=C#WYYV9((zskSRXB^GG<=vrmRfoEvXTK90DiADhmM) z7ke+509s%iylM3$10sb~Zq@vxq0(h}lS7NZ%O=w=k?uz+eh|w7aedOU%EJg(rDmBgE-GegZHLye3%tU?``ED2S166J0 z>J+Nei{KPv>K7X!&^y}oaKC#@rr%ZU$sTc`ZrX)gXQyM8ZBNQ*8rLb-P16kn&TUF; zFsxIt1CyGFDKb$(?m-DOGlboBjYM98J(iD7%_Hw#fANQ?FW_{O0=qcb^zNsMPfCMJi1ctJw^OO$L0++1B#T~jfW8nsL3#Z{{D zz4Y*MT+Th+k0f~29+y%w8ylObM?{xlN0wAu8oUnEy3A){Kk^3MPmwM&U7jFzCFOD@ zQ+g{eqESB@FFwQ(MAmsjq^I`m9DhT zy48?c?&$T}q1iYiOl)!ZcD%QxxbS>})?zsVhy>{ji~`8v`&I-(LAX?BQQF+v+FNS3 z*5szqi;|yevnnmThZ4(&iL&@ZIT}H(@>E32O1v!qDdV|PwZy8jL&Z}ci56y3<r_qyo_QYSXmwsk#HsS)=+M;k_j8VlZVm@FDlW&Vr zRjh@bk)ZK4O^hZPlIVzElPeA*dq%bdt0L5`UfW_OuDbYb%*qXyk{y(Wn0>xGk`WBIjlRh0?0(EFTC1V>Vd3@< z5oA83c&vz4KaS^%xtulYZg$jj_F>W07H^J|t|wE=LcAOdwVz2(t`vlx-;_J%>itg2 zenYskvoimeQ4C9!L92N&#HMq~ItY+q{xMjJ7GVCi6R?{yaYXFU=7Ei$lx>-y5*#;x zjclP|uP*{o$k1Nu-X5VStM_A*cHQa84*IU@+vl^qPD8wh%v*Q&Fe|IMPMg{L-G{~d zvB;UcJnPeuTe^jNdyn~rYUhcg2a>4c+|^BnJIZ}~g~K%G2~M#6u3Chrxf~z;QHcsy(4*xx_4;vr9JV8JGtfT2V*@H>fd8rTv4y9l6urG zyCUV#XNmEgJJpUiI1c>q#ag_%Qy!nJ+&F~x9h)9d@luD+3r!x7sl9(>0a~>8xR+OV zaqwox)8;bhBAv5X8{_>PlJR^z-y9j2c6HO^>cV};_QhRNvs3pXZ{6wS+{x`~Y%X59 z>F8d1BkwfQm_>hJed60k&=Lw;am||GX>lx@=yy86@IYKr=HLT}V)s6b;J^{n^>B4I z$dGL6=q?|a$PK8>l=9`3b$$XfV~JTXh(ob~re|$d0EzCS9n^f~Ya442l<19tr@&RX zivh~MpalK!ZUvDqyhZm7^Bo!x8XN|=W=dI)Mlt5o{P13;IOPuO4xP9Zd@@}Gi0 zNNEt~N?3qEZhvr&j4)L_9aVYoWOkBk1pZOdwfhds;k~Ed{SoWaw+ZhsU*jBzBt3F8 zZvQVG6iHDqp0V)LbU!2zf??Yv5X51MV{2tgKoLEnYj};PYdjfOzagrim(MWCF800aBBp}UDAneXiB5c7g6C$ zK|ZfvYRH{^f6fz5v+ATaWZIqms-iK_5UL}Zi}CwCDUvu2#r_79p^np2SBlA&P&V|_Di+B!jaKq zKz7j-AVQ>YWMyWL@uvC?mb?0ns(z2rt#rzGwQ!_k`~D})^hoiKc&x2yj{;;2`F))>(+dz^qq$EW?Kw?i43n`BRNV;6)>~p(o+Icb z_nK4@v^X`^Wa~Bw+cqWx!BobCGVO~S%kYQm0FwW<-_-lj-M10n^MbcL1=Hy_`hod3 zEI&Kzzv1GlB5gg-hS}TOSdNq)8gT5=mXRlkM z9&yDDomJBdqnj-_lDnIjY(sH|kcC^vi}dyNrrWD_kQ2A=kop*< zva-vQBZhcKoob6L&WDRtyxzeZjixU3VXFgo$x4CSj~6oS9D7%U4~^>Zg>jvF7EQqB zQTSKeabkBD^&$M1^!enwX=TY*=NHlmrRw__0QHc7V6q-lBpjly-OYWNXN$zWhqQ;R zKUIf6Y$rOiiulNP_PD=`1MfUBk*xGh{W%0dc6;+1lb853Z!A64{jS=gR}32dB7p0U zdxI%KHeZa@saS+@|J=d}TK!bj%R1b-Pfsp+9pDIejv>>#h#0$gffFsUU;O>fE6LyV zVLhRrZS!<@fSO2^Q$r!m^6Nk@(mW0jt}X0}uDX3&H{YCSvMs?{_JDNP=RCeHape`= zvGgdGeECcfwNJXxlO_b^{p2KT7(NlPap7zQ>Sgm0z({1ufiV$^9N7+HyNd7xIm-lX z%tR4DlRMb(=_1QSZNl#Y_*Kcmrd7=Yo}EJL@+Xk*m^rFcwgJAaCW3ik<|}aP@ND{m zgo@i?1v35dcDnJ*v1S$jP!7}otQ_(ozUq;Kb9VuhgMI>xX8>-IZhQtF6?+d_a zFtUzg%-w7hN)kSOt$5K66#(tflMI_&wCil|8a!pQk(G% zT@Q}tU_2m(9<%O0U=|%fTBm;#y4Bt#@}gq1X}^wm1t?@R@8u?lu&?<0J!i?t}$+gjO;W6 z9Q|l{-li@0rj~hFHG6FB6rzn(BH-7ngZq&JegTiggxW8o`{r|juGJ9jx6eq`u;9re zJcuT_)bX-E$3le!EfdmmZdUJy*e6Yv&tYaBY0!Gn;pc0=+SXA?axv`+_ z*ck5zk2&02>+(mlFeqOutfWpn?&Qpzy`VT%L)k{tWM!!bNYgLUYqqS(mog=tJn_C8 z_cIf-$H%#Of>m0*gW9XByIidy9kgR-y(k z+@4z&?_F-y{H$XFXR;#S*4HXAKK8y!_z5c5Splt($aacQK!Y)PW3}kQ*c>f7ny%iiYV# zP7;%DNZ7tvZEIuPph&KOsvkAp)q(3Qx`^s~ z14yby%}l>{R#~>!fzTvSCWlla!?OwoY871=Fu8?R z75iC4{Rl%zs>>tp{I2?ihCfMns&(-pH7{@Z;x`0$y|; zN3>B5d1GJa+{|Gmy8^~<8hcecg$ik7p^9l^S&C_=V|K5`Leb`=@c~dCAaE@Y5V-bg zQ-~bPcm{ao0Rq|XfoqCsIW2bplGHO`SlzRy0bn?I6$5kxz@|_v9EmOj1g@!j zqP5&nd9*|}>pU)SJpSlcrA{`&=dr7X2bZ4az_1uqhjfypcTbg|$S@BdJraLP;vUlZIakKGc$JGLwFUK)!HHDF?*Z%YVdXX^y z(ipBq9#1QhU#)?)$Sl6shT|xp3~;n|l#;r&^3?S_vD7;Lh*S?nq^znHQvKX&^qobL z5=;2cEy$f9{Wk~`i6AuK<2-9&AOMTNiim~_j7a7?FhVH!=PLeiWaa^6SjBV^kYH&M z3Z&jQ4pmJSUE))p7M)On*a`r)Loz@!2uh>@4t9712e>xa!UAjuo7c?+4Hi9Y1BY%^ zi#p>&?Z+sZU`kghWmR4+1F-BS%$3_?QjhPr#9l5VN{ZU04F>M*j{Ya<|~%TB3Vq9ttVANZ6Vz_sI8R==0jbqGB1k+z*OMO?x?iHRdD$0`t=jMEH4$jjUK)+JRi)s`LZ=19wbJseXKnrwE*CoT6GRK(F`)(Un9%y=G0_b0m;hYL z0E#-(0z4*Ye|by*vQw%79uwvO$9{DW@K!W{#{}9hkBRDE9up5dSN@y!`ZvmgF0pW03(9BhgJ&C-rTEzDlFfSZiVU0mygl1-J>Hyix|sn@Ye=oI78$< zRlDxUxty)VX0io0tkY0!EkS&nKIJnT*tMbyXGP>WATlSb^672_*qQfk!@KJVqu_V? z;Wus+tp65|E^YnyxO7WoI%O27bHdFs%PFP8=G+yBFUUbYICsZ0)&Wd~X6|incuPOH zc)b@MT$YB%8{F+HPqaeNSsk`p>?#koj{xVrY-l?2G&p%UlsH{(9-k1T-j~cZ$S)Wr zc}%dZr;u6C?b(zpe!ldMc087L&qL zBYCoW9CbMKle<=sz$c&M*pK+|1MvqLzn<5ZI_tTHG^Jox6WACuN5$}ou%5jgP5!eh zeT+rhiK9lHYUFeDXM2o$gmG8&A8B`Um(uqDBt7Juw|0ACcZEQxPp28Jdj39fDX@2O zEclj{^3kB#D>-?TNwuhW&x|$M>D#{ZVIz!+LP_^`su^GNecETeVV8N?NyJIIQ@Bt*R*4su=jFl)rkHIEIhkH$dS{paZ5Sqx)~MyrcTl*b&29zuys;mpwTlCo{sIq8LGMkIF@tR^LQEBEo29%ZOU5x z`c^%q0dPh`mhsV9gWi=AxHph8w1@V;3Ihx4--SWR)y9y3R#wkU(ZLd$R)&C{j_&37 zBRhKs0(K?_Xj&;lD`N)}0%kTkhF@^qe||X9p@ymTyx92Dk#>7zql@mPXEP+4O^G;Z zztkG>SD1Ez8qAN36&qr9(X-mEj!Xr)2K_N9L&Je!MJkoEqEm`fj!cGb$CPTWWR%p7 zo1WjLe5~W%8?SQqz0ZGhSP=qsTqq&FvjL)1ML(KJX^8AZ|ILo)(AAm})xw|0hLR%x z2C}jj@PjGui^GbmrsbeUGvTy!fNGhX!fXb~M*0h@bQ;rQVLjsx$oK2 zNyMOB!0WoezA?}Xz?~?jn&I)O_;AaG*!Y2d`h+z1u@&8iA?K|Wx(_0l1z3JNq~AD< zPh$lZBs`YL2Z$hWKFwK^GVpiF7a#nP;0^H{gn&9ybaebfhY!F&-Zcf7CJ(-QKF)ngnvy};?w$0 zrH6=XV`8%y!~pa@0}TkyP-lynKN8XA8(%FSznng-4s;?gbKstd&v%rf*$Fgd`ixYy zQJw-F61WTB;R#4NLQ`EMIJX=iv{9&CIb)K1a`J>&D3>yzDOL83cNs+3b>LIu1cued z%wcbcbX$Dp4l}kK__%wqOk%;e_-@dSN%O%1Ir%^3g2%sgdUJx5+?&{|-d3DpnB zK}I_XD<5;D3Y^|u9R%Z$4m43{1HKfgV*DuxCYT9f7T+?BcVKSwtx7$&w|h#f^i$0( zPp$XDeJ?Mxj7c-MtuqH_fw55L136xB?;4)Qn{)gx1?$3>K%gv;ChnPd>GBog2vk;k z8xUpS7w)dERX*(96Jr|)wl>@F#RTvX=vli$O(>-Z`0Lx&#-ZKa56_?35+cASW((x( z()pk}4mc^V!)pf)le`mKK@wQAP$tb0->H`{mFehwlK_DVfp9W;Gxv<1IcdUelfI!f z;0pp~MX&6n<5$m}L(%0~i7-%2ny{xIXNF9VE9J@7wY(JrFMR*j za*`09_ybPT4+7M^6=J$GZb+WgVYAbM7P#4p`S?N&XSYw4vfM;0yW{E@Qe)z(VI6p2qGLW-7-rTQIaHE9d3s$SFbfK*GozCHz3~+TVa88a;@LH*0&Ipe*96tQ|I9qfUI% z{SJy{!Ca3M$!m2~RKO^N1Og*>y?2m_xieW=8gK|R=2<$-@&>hltRKxo{eZKw9T%E) zU>LPad42a_v-_m3ge}0A+_XI3_-lwD#_7uZT3%e}YGw)B*6dpZLX^Uzg@P`@B=X@L zF9h6NQP;&l#3@~=5cINW7oH@%)*^mMox6Jk%G2*pH71!^hVsg5YZnvU2N(HwqtS#% zGuk4(m#ebQylYDMF<)H6$JW-DAc6!SbgG}vHyvl5DZMR%D|*x4wt#?Qw$zt|AkVkq zv_rnv_iz3tnJO-*Bktoy@~VL+V>$$PjB4H`ZIMK#F!^j1R>yEj~*?4uK$P5bnD`2@!qjUhrPUudQ7 zn|qeBF%Vt)rhx66;w2$w8a)QeGg5Tb>_wv0`LO*t;3K7dw(>!05+PAsL%$;tDd0iT zn{#G;;#D~Xc@2bH2 z_FchRg+`{{H&OOB3`oLu>yB7PrmR?hCbOt;u>v^qcad&-UyKTsj<)$JiAr#Y4BhEO ziNiB2;oB(&P&XjjcLSqCNCLu&==+4Np;j%@_2G>=Z8e-Tp2;F}8Av-y$Z^G~YNLWf z-;ZC3L=cJ)2T#CHfNzq@pCjv;aqSrh#6%M}gK5H5*{;Gg3Y7r|Px5cNUF6R#5zqln zsP%6uQ}9>K{WRdiFVb#bg2cU$Wh(BfAR6#~hM#dPh{%#H@gpN%(}Y3a3>4EG)B0SC z3x0*mB#bD@Rz^3s2$cB=zqt4~W9BLg$w(*3zRbxW$8|q70dH5Zj|wr__Q?uby?vq+ zgTnBml+fGoZIt%62w1nl1@XZ~o2RZ|(va>|DXw5g;)W7zva_G=rw!)h1MP>XMPtcV zGYGw6RXv<%RrL5p!y*>Kh=_x}BeN&UW&x{XL@c|&y)a85Ft!C!;$oKkLgdn)j%~nyNM=BwgSS@-lH-XY7tZ!J<8MN)x#K{ zEysj)ek=qYv$e{Cm1Jxz7!?jWBufMAJ9T(=a!Q;*l2zXWl0rq8&LW9cS7cmX#m&(% zF3DCwu2IW+twWDp@llPSThqvAU&HAA28ZWBZTd=}nl?my29Xcaz}7^BIlm!+B->yG zqc?r5JoR9oQv_K5j&4i*RKTrTuhX{?5%!t)8KPmz2GXTTWIwL{8|yX47<-gq2{Z+c$QQrs>bavrE|bmkCj zw8U|>uQ!Z}`0`1?Y+x&3K!o| zLLpBdHzB*fL}|Vah4RWw#!@8o2{g= zI9F5~eO=^4dARybb9Wcwaw8~kpbv@sTXwZy1$q>xyx#c~t3rD<#rb>BJmMcf96nTb3^?VWH;;uYgo2t7e?ti68O zWPIq22h$ru5ayHAn7Vx~u(C0oPdF5(XR&jrx%QAI*&M-XDo`7qsE4xDocF@5ZDY4ODL+Jd*z{Akk6M%T9qIhQ zqcTk) zhVfkZ)8q}^u@APwN&U->UCX523_&FY%;(^T6biXY(DPD1$n;|s#3Un#H@rw)B(%l5 zWWLB?D;UX|9oQ2Lt($r^9Fx~rtE@A8(~pO)-x#R^ve2rBg2nYtZ1^&`AE4SaeG<&n ziwuKRXu_x~J0wn8bH`N(=YU{{hQe;b!4?|Nu7+zD5_AP7$j_9d(r8GI*LO?VGpsls zE;7!cbzf;S{)(IV%lxY0-ROc6dGCgO`0j<4?P;-0| zQ|Jca4s1mb+$G_G&G7@A{=58*q{@k=wDC#%R$mfUy^`@`tU=>P$`LHm-D}IwvO4!s z?wQ(cBX)7CvRHA{A1D{j5S(Ubuh2KIo#wXg*(`_mlPYPOuC;Av6F!vKC#bfZd32+M z8M4gy{z&gatEsRMttDWvk-SNiHGa}V(f<&O5uGPu(C1xNC?UXAtd}?#0gM@LRkn!c zVAsLjV3bf!HMqGL8wxz449#ZqZi*?1mp=2>2v!2{vwF8$v}&Ya!`K5c7Zsb(SRbq; zdVZRdF_iC1#TjDil;%Iy)Kj!{b<#dDK{!X)L*$$?bz*sB`oQ&IxqT811g|$|h|jj| z?+lxo6?2%c$J@n(*e1@P*2G@e`ZkBPTrZtqoXx-s1}#mU#t0fWq6K{LECvJ$u|OJF zMFs2;#aW4+iywsjK4Y+$$ z;V{tt`cR`&ULx3Y8h)+Te~X>s=klp`!K z?|i^+r13&=!xP)Hy7X%zYhe#m z@23=3H#T67Pua`Bj@}eCn1yNZ@XK7@OL{rAx~PbSkPtunY2#pK-=5?xY*K^)vM~g=E%OFQ-ZL!?`(tsz2Uthf)>yKht zHWC}BiQ?jEx<4;8#ud{;$PpEaiC%{Kl3q&${K0`ui8J9KkT$J}t?6T3ZSjKIJ6qZH zSQV3M$6hmMZjFQO)f$Waag%2uqx;j78#{UDqU%z!EH{-A(ensg@7Ae+nQgMp+k||w z3ox{1x;@o~5nJxGYu3F!yDVSy^}*X>>IaW72d6_1^95Du#<~6~e6IW58Eq{T8u!Kr z=ETOJ=hE1NkO$U;i}0%jOQZ)Nn1;xQ`T`|CbaQyzDcwHYcG)mlGr5SY*^`?{H-6-P z$JmZBk|0yx0eg+Ku%7i*Qfj@+0$y$SUbSGkP&%rgXoO+$5zD12R4we5^|vr4>a9Tn zha0Be?rzT~yYnK49n9+JaO$|y&x670#@3dO9Y%$IL2vIhubk|1j+b~F@1Ha~mzZff zInrh;gTuZza=Uwl4!U?MPty^RG!v=)%ymr`xTuuiyj=|B<@LT>b6x!wKPY|EeGmit zBzs7GLfIKL1PKoWI9g=CVKsND|C-19Ze@4S`Sao8K#nxKZpXy*arb7;=o&r7Sa_nH zu+ywjY#Qx8`(vr>%m7t|myeSJ|9ro0bJO$IWu-GavHR`z?U(~sE6ZD;9daaj7baZj z4FZ8iH8~?zh+WZ6wYM=9)@=CFZA@v}fNgK6EooTRURyy@`Of&d#zcypYB`*4OTqIzuLu)N@Yee=i<-V!;& zpuU!+*wuW|r{HMQJBU{oXbJ8tNxm$ko#c0&)O)CjS>)D0`G!^X=QG4teaLOnJfDu8 z39>S%w;tcxAvSCM&AzEvx`@|HPXD$zsaGt9+k50l%9or0NN zJ%L~|(1Mchh^>k$(^-KUBlWP5;`&@k96gYfgYN@Zo;v6lxl$4`8w%Mhk^44VwIB6P zJ16R!zb8r{L#wmL#P(HNWw`vwAr|{Yew71zW81avMiH>KU-=KJ_r;pSMCn*|q`uZp zri@^9knC-}GtL_}c+0iru)Dlh3X$ZEdFqcMzH=vpVmaJ+o=P+3R*&i+*Nr9>BVZznO(6uxZlt;**)vFxIFDpLwONtjXi9Dgeke1}vVHgdbs z>&Uq9l;wObJ)`hkGn5j`ZI$ynG0uV3#KX_HPxLLcMB#*cbqlrYxivO z61b2jw!$tL+vJ(qof|Oc4Jh@`038eL;-rX2RnzLis`two%C%MCch`~TrqzuT4Vz8o zpEaseG{S-8VJvq9OXgD+sCyX;H|`EM^d_gHk`(am9*;JxWcF1H!p+^1pv_J*30ZD< zx&ju17n8F~5eqlpmvdMx7<@;lJ_QeHq$knJaatsHs7ZK_LU`4|awv1SHR<$C-5su` zL2jOBQoWwDI!Q4C)odt{^h2%qTZIF`;<=Q$aNE20<>nze9i)1K_{&k+dDU;rx$$sI z8iJVx(G2-nw5kx3l)rz~%DV%{J8XdPEhH{gV_aOc%PIVj#jeGF4#zd`7WPIw7>?d|0?4K9PemuJx}?*CU+=@LGX>nXww#lo<`m+ zc*IeIewK2yzIC+zo}k@Bxo#qLzhg*+@6j`FL0gQ7Cn8EtxUvyw&o-*i27G(oR#hGQ5SM{)&`FHhIRzx zHU>sYh99WuX_)9}7}zPGY55%;Oswt5H5gf00ROBSbaZq|FNw)ZV)~Nkz9hykiSr`PhYMgJ@y^pfrPlDuZyy?k+%tSh zUhn;xZS<1!TFq-?e`ddye$9Su`Lz$P?dVd_Dmv z;%h|)FPE>=>-F-_?)~*RuNA!}ukYfu3;)#cf7g?|PKCdow(F z^mMJPUUY?$iK#sS;P02tpdwH)w6g~^O~6P)&qUA6&cMRVNXtJGUs7v7V#}`>>m}nR%pm}&+^p&-q88j>N--A~EBZ0@v_9uVG#0H@9 z{^0NE*;wiStNh((wRx)z7G%$B)f4(kzMdv@C{UnJprN2i*jHyL8~ioQsS$^;&z8wH%MA{5p!N1pjs z#vJLJ%~;Tz4tMvrVQie%Gihl}+4>~~_HS%J#g-9hm9DR;qqP*n8s z_u)-Sriwc{o@dWVy|ZOC5LoN*c5*kp41xJRVg@E0IRo)%y(MqkU(nA+8}j;y0G6!j za}Rn)vsr+6Y72iIt$*XY4mj(LO%&r6?rqMQx>3u0gObrhvxc~n^HmmThtF2n*WA>3 z^UOOnpRM-cJ8T+0npkAae}AM4u(!runD1!zHHx3Dq-T~Q9FO-?ECjDBl(7VglNp-I z+fwO=zUC-F10Br42HO*;b%IDuwwg%4Z=hmrj7A^awaTowl4gVKthcuMvERWoM0YM{ zc67+FAE`oO+^HNIecUfKIh>$*-;_duAQN%gdz6FUO`GP)xZwSiR?sMQT-LTh!*hSt zNhh_fySH5EeAAF^yz8v^EC9?rQbILKd?KYI_C`uap3XW=yF7kk&{Ta>^M{wFzNDS# z5tpyG=&_yD8}guNC}5~bsybd>%*ES`*06P(9#wOtJy$xE%{1JWOuY}>wRnN`d><05 z(n&8ho$D_qe8d&#dZQOEx66}|$R!8lC}wdu}?n7<9sUKw5H9ML+Q8g z-nHXx;z~Q(`cyP`(gHSjW-)VZv-c!~F9<6VTm5WJ zao58tM&R~Q@?66^xrTyoy1zPF zgO?tzMG}1fn;#dPnM$b8~3tNK1#gs>6XnD5GKzXOd{g-x;IFeA2?P|JNM7&!bkGXC)}@7^PLZu zWJjl`v7_|8Wo1uz$FtVrPE&H27(A#m0tKgu5@Tx6W(I<^2Xd-RWXZmJ8AQ}9- zbzktkibo<|5{=fokBvCr^c3AKGSogDcnU^D@w03|V%9)xl6#H9vG*W6rN{VN9NH;` zyfcw!Q6Q}P`6)o7do)VXZvf3`PobWqJC8Z6GhEjN$7qtsbR zIB8rHGn2&Qe3K$tS-0ZML#qvbHK@%VQ$JC`U?wc314(t*rNEoo zc@P&qxAx+9)*-jw&y=?d`&Hd91g~}+naKutQwezmqrBe>E=jB~B1Q$$eogXB=-gHY zj{Db-i4U&#-^%47Jhz<~*{8yT&=oYa-GF z_O|ijEN3HfMr*-qQF`(GL@Z4VuN+MGR^)bK-rt=`Jl2}DKy>xa@S*%!*Ja20ne{4^ zzM=VQyHp|PX6KtVpEpo>5Zco3HW~lAe1A|dG=l!W3c2=hCKxqtHXAdvCD+AFi!|4n zOlY)KuFW+=nM9adxg@y^k@OsLJ0`bLn9nDZTuQO&f{K>QOy*X~B$uy9rS$1J=Q;nJ zKfeFo_kG^W^ZxgHuZDIJX9D9s=_SO>wQ%P7viSAN%~*VUpiZ!=?p)R;2S3~>K@J-g z<_znn+J}V;b6!mX|KP|49JIe+D7>t$n8)+6kYa0BdO)tpQeYtdaSfH)LnLKF~sQ?7ka7+y`0tWGr9TJvIi0lly1Ej>;c2vxRdJVX+ zFZHzbl^nZI3NMPeddp#t8@dE>VS6g4%Yafq7!JfeHHoD*B5_g$l4`;jM&B--Sk3MQ zE6AsVS?bj?;s=N1W1dw?SWNgx=p<0bTLPB{d%Z`s(7LIj8VvR0Dwl{6%6>`rX8q`U zSruvh3RDN!u>A&`DKyl?Z}gkUzq{)1+4M>@XH$U zk7IS@mYADs>Q+SG{ugl?!%kwoss@q>@y1@P=fT{(RFLW3>|pSVtFzO^N!mcGgOs>4 z*3LHJYUrLS&gC9Ns6BgMRqf>$uC4b{^yxus#cT&ekE6S`9R?(6r-yL2V&#v>-x&BA z>AB*nnt@?mI0$%sY!J#@yEO zcevh%@sD_$sbBnVce&6L@X1b$nFQ;iPvS~f#>`C%`4n4gnQyoBSu^3xMzdg~nmv2Z zB&1I#@39OXbCQbTE)=v4m?bxi#f`67wQOuXI=Q%?r@^qd46La0{G!-fq5evFpu%Tp z<|pGOckycz#C^6)@Nqtce;v&I_C_IHdj@1Kf8)ieP5Za(-Bu@>A^~;W5I%H}(L(G! z)-wLqZvjtwd(cK%41P#cq_8)At-B)yw9wF(^>6CUk~ArBcDij%WvJET8IRe4Gy->N zzFzbriyH6w*=6jTj1kaa#?Hg9>_kQBq#vZrzhYu)F=+Y>vX$Mtv6VgE*?A*(V#H15 zdn~KPIm86Ti?XL5)(I}|Z(aWb?_a~Kn`Yh}S{qi2erCTCQ;!cX@@TSX5KR}{{IZCY zYnnHA+z=KMoV)UGw&IRXC=)E66A~7_T$oe&f$i;kPI&4Q?buZk@P3-`o*s>rpYx(V zKetX!`j^^1$^!_rYqd|WvU0RrHsWQ}rOXMOsABdnSnk<)%i-=@$+b~&nJu`o^4X8D zvuD3IqP0v$Di)gZVSQ{Y?it@PrDddCl~9nkLscekVT0bIm-5N8OH`S(k_-YzoG$%; c6cHWce?BIDyCnuSHNhF1;h`7|`S=m&-& Date: Tue, 28 Jul 2026 18:30:43 -0300 Subject: [PATCH 83/83] docs: put the paper where a reader arrives The article was committed and then reachable only by knowing it was there. It is now named in the opening, in the brief table, and in the documentation index, because it is the one artefact that argues the protocol and the measurement as a single claim rather than as two sections of one repository. --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 280f39cb..48406857 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,13 @@ delegation or a governance vote needs and what a transfer cannot do. Rust end to end. MIT. No Anchor, no Circom, no JavaScript anywhere in the proving path. +**The whole work is also written up as a paper**, if you would rather read the +argument than the code: +[**A Behavioural Anonymity Set for Solana**](docs/a-behavioural-anonymity-set-for-solana.pdf) +— 39 pages, 26 references, with the protocol and the measurement argued as one +claim and every figure in it traceable to a test or a transaction in this +repository. + ## Meeting the brief Every row is checkable in this repository, and the right-hand column says where. @@ -31,7 +38,7 @@ Every row is checkable in this repository, and the right-hand column says where. | **Deployed and running** | Live on devnet, with every claim in this file linking to the transaction behind it. The full lifecycle — pool, deposits, proofs, batched settlement, and four rejections — is recorded with signatures. | [`docs/PROOF.md`](docs/PROOF.md) | | **Scalable & customizable** | Adding a protocol requires no change to the on-chain program — no redeploy, no new circuit, no governance. Selector 1 invokes any program with any payload; selector 2 additionally makes the pool *sign* as the member's authority, which is what a stake delegation or a governance vote needs. The whole procedure is four steps with a worked `DelegateStake` that runs on devnet. | [`docs/INTEGRATING.md`](docs/INTEGRATING.md) | | **Realistic** | The anonymity number is computed from live mainnet chain data, with the sample committed so the result reproduces without RPC access — and it is pointed at a pool this project neither controls nor funded, because measuring our own empty pool would be measuring nothing. | [`docs/MEASUREMENT_LOG.md`](docs/MEASUREMENT_LOG.md) | -| **Well-documented** | Eleven documents: install path, architecture, threat model, proof of life, measurement method, and the design as decided with every departure from it recorded. | [below](#documentation) | +| **Well-documented** | Eleven documents plus a 39-page paper: install path, architecture, threat model, proof of life, measurement method, and the design as decided with every departure from it recorded. | [below](#documentation) | | **Open source, MIT** | MIT at the workspace root and on every crate. | [`LICENSE`](LICENSE) | ## What is different here @@ -972,6 +979,7 @@ reporting. | `docs/THREAT_MODEL.md` | The adversary, what holds, and every place it stops. | | `docs/INCENTIVES.md` | What keeps a member in the pool, enforced by the program — and the one reward that is deliberately absent. | | `docs/INTEGRATING.md` | Adding a protocol: the three action shapes, a worked stake delegation, and what the proof does and does not promise. | +| [`docs/a-behavioural-anonymity-set-for-solana.pdf`](docs/a-behavioural-anonymity-set-for-solana.pdf) | **The paper.** The protocol and the measurement as one argument, with the related work, the method in full, and the eight runs. | | `docs/PROOF.md` | Devnet signatures for every flow, and the rejections. | | `docs/CROWD.md` | Six members delegating to six different validators in one devnet transaction, and what divergence costs. | | `docs/USAGE.md` | The member-facing commands, end to end, with real devnet output. |

| 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