MNT Price: $1.18 (-0.67%)

Contract

0xdedf9475D447fC9a6B74F0F93F7Ac20e8F400d2F
 

Overview

MNT Balance

Mantle Mainnet Network LogoMantle Mainnet Network LogoMantle Mainnet Network Logo0 MNT

MNT Value

$0.00

Multichain Info

1 address found via
Transaction Hash
Method
Block
From
To
Query NFT729962912024-12-13 20:48:144 days ago1734122894IN
0xdedf9475...e8F400d2F
1.5 MNT0.014123940.02
Query NFT722135572024-11-25 17:57:0623 days ago1732557426IN
0xdedf9475...e8F400d2F
1.5 MNT0.018954880.02
Query NFT710148102024-10-28 23:58:5250 days ago1730159932IN
0xdedf9475...e8F400d2F
1.5 MNT0.018866340.02
Query NFT704735652024-10-16 11:17:2263 days ago1729077442IN
0xdedf9475...e8F400d2F
1.5 MNT0.026597940.03
Query NFT704735372024-10-16 11:16:2663 days ago1729077386IN
0xdedf9475...e8F400d2F
1.5 MNT0.026599780.03
Query NFT704735212024-10-16 11:15:5463 days ago1729077354IN
0xdedf9475...e8F400d2F
0.00045 MNT0.006626590.03
Query NFT690201752024-09-12 19:51:0296 days ago1726170662IN
0xdedf9475...e8F400d2F
1.5 MNT0.017734950.02
Query NFT688813762024-09-09 14:44:24100 days ago1725893064IN
0xdedf9475...e8F400d2F
1.5 MNT0.018228050.02
Query NFT688812872024-09-09 14:41:26100 days ago1725892886IN
0xdedf9475...e8F400d2F
1.5 MNT0.018097220.02
Query NFT687205292024-09-05 21:22:50103 days ago1725571370IN
0xdedf9475...e8F400d2F
1.5 MNT0.017419560.02
Query NFT687205172024-09-05 21:22:26103 days ago1725571346IN
0xdedf9475...e8F400d2F
1.5 MNT0.017430660.02
Query NFT686798442024-09-04 22:46:40104 days ago1725490000IN
0xdedf9475...e8F400d2F
1.5 MNT0.017653930.02
Query NFT686501722024-09-04 6:17:36105 days ago1725430656IN
0xdedf9475...e8F400d2F
1.5 MNT0.006506630.03
Query NFT686501452024-09-04 6:16:42105 days ago1725430602IN
0xdedf9475...e8F400d2F
1.5 MNT0.006503480.03
Query NFT686500462024-09-04 6:13:24105 days ago1725430404IN
0xdedf9475...e8F400d2F
1.5 MNT0.006513420.03
Query NFT685673672024-09-02 8:17:26107 days ago1725265046IN
0xdedf9475...e8F400d2F
1.5 MNT0.02095710.024
Query NFT685456142024-09-01 20:12:20107 days ago1725221540IN
0xdedf9475...e8F400d2F
1.5 MNT0.017070610.02
Query NFT685166332024-09-01 4:06:18108 days ago1725163578IN
0xdedf9475...e8F400d2F
1.5 MNT0.016995370.02
Query NFT685123702024-09-01 1:44:12108 days ago1725155052IN
0xdedf9475...e8F400d2F
1.5 MNT0.017028160.02
Query NFT685045102024-08-31 21:22:12108 days ago1725139332IN
0xdedf9475...e8F400d2F
1.5 MNT0.016950020.02
Query NFT685004342024-08-31 19:06:20108 days ago1725131180IN
0xdedf9475...e8F400d2F
1.5 MNT0.017027120.02
Query NFT684997802024-08-31 18:44:32108 days ago1725129872IN
0xdedf9475...e8F400d2F
1.51 MNT0.01873690.022
Query NFT684945292024-08-31 15:49:30109 days ago1725119370IN
0xdedf9475...e8F400d2F
1.5 MNT0.016904720.02
Query NFT684938922024-08-31 15:28:16109 days ago1725118096IN
0xdedf9475...e8F400d2F
1.5 MNT0.016957960.02
Query NFT684937552024-08-31 15:23:42109 days ago1725117822IN
0xdedf9475...e8F400d2F
1.5 MNT0.025374030.03
View all transactions

Latest 25 internal transactions (View All)

Parent Transaction Hash Block From To
729962912024-12-13 20:48:144 days ago1734122894
0xdedf9475...e8F400d2F
1.5 MNT
722135572024-11-25 17:57:0623 days ago1732557426
0xdedf9475...e8F400d2F
1.5 MNT
710148102024-10-28 23:58:5250 days ago1730159932
0xdedf9475...e8F400d2F
1.5 MNT
704735652024-10-16 11:17:2263 days ago1729077442
0xdedf9475...e8F400d2F
1.5 MNT
704735372024-10-16 11:16:2663 days ago1729077386
0xdedf9475...e8F400d2F
1.5 MNT
704735212024-10-16 11:15:5463 days ago1729077354
0xdedf9475...e8F400d2F
0.00045 MNT
690201752024-09-12 19:51:0296 days ago1726170662
0xdedf9475...e8F400d2F
1.5 MNT
688813762024-09-09 14:44:24100 days ago1725893064
0xdedf9475...e8F400d2F
1.5 MNT
688812872024-09-09 14:41:26100 days ago1725892886
0xdedf9475...e8F400d2F
1.5 MNT
687205292024-09-05 21:22:50103 days ago1725571370
0xdedf9475...e8F400d2F
1.5 MNT
687205172024-09-05 21:22:26103 days ago1725571346
0xdedf9475...e8F400d2F
1.5 MNT
686798442024-09-04 22:46:40104 days ago1725490000
0xdedf9475...e8F400d2F
1.5 MNT
686501722024-09-04 6:17:36105 days ago1725430656
0xdedf9475...e8F400d2F
1.5 MNT
686501452024-09-04 6:16:42105 days ago1725430602
0xdedf9475...e8F400d2F
1.5 MNT
686500462024-09-04 6:13:24105 days ago1725430404
0xdedf9475...e8F400d2F
1.5 MNT
685673672024-09-02 8:17:26107 days ago1725265046
0xdedf9475...e8F400d2F
1.5 MNT
685456142024-09-01 20:12:20107 days ago1725221540
0xdedf9475...e8F400d2F
1.5 MNT
685166332024-09-01 4:06:18108 days ago1725163578
0xdedf9475...e8F400d2F
1.5 MNT
685123702024-09-01 1:44:12108 days ago1725155052
0xdedf9475...e8F400d2F
1.5 MNT
685045102024-08-31 21:22:12108 days ago1725139332
0xdedf9475...e8F400d2F
1.5 MNT
685004342024-08-31 19:06:20108 days ago1725131180
0xdedf9475...e8F400d2F
1.5 MNT
684997802024-08-31 18:44:32108 days ago1725129872
0xdedf9475...e8F400d2F
1.51 MNT
684945292024-08-31 15:49:30109 days ago1725119370
0xdedf9475...e8F400d2F
1.5 MNT
684938922024-08-31 15:28:16109 days ago1725118096
0xdedf9475...e8F400d2F
1.5 MNT
684937552024-08-31 15:23:42109 days ago1725117822
0xdedf9475...e8F400d2F
1.5 MNT
View All Internal Transactions

Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
LPNQueryV0

Compiler Version
v0.8.25+commit.b61c2a91

Optimization Enabled:
Yes with 200 runs

Other Settings:
shanghai EvmVersion, MIT license

Contract Source Code (Solidity)

/**
 *Submitted for verification at mantlescan.xyz on 2024-06-27
*/

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0 ^0.8.13;

// src/Groth16Verifier.sol

/// @title Groth16 verifier template.
/// @author Remco Bloemen
/// @notice Supports verifying Groth16 proofs. Proofs can be in uncompressed
/// (256 bytes) and compressed (128 bytes) format. A view function is provided
/// to compress proofs.
/// @notice See <https://2π.com/23/bn254-compression> for further explanation.
library Groth16Verifier {
    /// Some of the provided public input values are larger than the field modulus.
    /// @dev Public input elements are not automatically reduced, as this is can be
    /// a dangerous source of bugs.
    error PublicInputNotInField();

    /// The proof is invalid.
    /// @dev This can mean that provided Groth16 proof points are not on their
    /// curves, that pairing equation fails, or that the proof is not for the
    /// provided public input.
    error ProofInvalid();

    // Addresses of precompiles
    uint256 constant PRECOMPILE_MODEXP = 0x05;
    uint256 constant PRECOMPILE_ADD = 0x06;
    uint256 constant PRECOMPILE_MUL = 0x07;
    uint256 constant PRECOMPILE_VERIFY = 0x08;

    // Base field Fp order P and scalar field Fr order R.
    // For BN254 these are computed as follows:
    //     t = 4965661367192848881
    //     P = 36⋅t⁴ + 36⋅t³ + 24⋅t² + 6⋅t + 1
    //     R = 36⋅t⁴ + 36⋅t³ + 18⋅t² + 6⋅t + 1
    uint256 constant P =
        0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47;
    uint256 constant R =
        0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001;

    // Extension field Fp2 = Fp[i] / (i² + 1)
    // Note: This is the complex extension field of Fp with i² = -1.
    //       Values in Fp2 are represented as a pair of Fp elements (a₀, a₁) as a₀ + a₁⋅i.
    // Note: The order of Fp2 elements is *opposite* that of the pairing contract, which
    //       expects Fp2 elements in order (a₁, a₀). This is also the order in which
    //       Fp2 elements are encoded in the public interface as this became convention.

    // Constants in Fp
    uint256 constant FRACTION_1_2_FP =
        0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea4;
    uint256 constant FRACTION_27_82_FP =
        0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5;
    uint256 constant FRACTION_3_82_FP =
        0x2fcd3ac2a640a154eb23960892a85a68f031ca0c8344b23a577dcf1052b9e775;

    // Exponents for inversions and square roots mod P
    uint256 constant EXP_INVERSE_FP =
        0x30644E72E131A029B85045B68181585D97816A916871CA8D3C208C16D87CFD45; // P - 2
    uint256 constant EXP_SQRT_FP =
        0xC19139CB84C680A6E14116DA060561765E05AA45A1C72A34F082305B61F3F52; // (P + 1) / 4;

    // Groth16 alpha point in G1
    uint256 constant ALPHA_X =
        14203975404520260752093969438138389323271181511080380152435798100099745969379;
    uint256 constant ALPHA_Y =
        18287204318794171332400073719746908786851491729756415454223442198102849074855;

    // Groth16 beta point in G2 in powers of i
    uint256 constant BETA_NEG_X_0 =
        14182746057118932531119419775248655215999552851587880769894092919815318854079;
    uint256 constant BETA_NEG_X_1 =
        12683390308506128326666583226496302601857318988227740948513564592565150412253;
    uint256 constant BETA_NEG_Y_0 =
        17702544229660055425992089692161462261736050259607705457555294197497668133802;
    uint256 constant BETA_NEG_Y_1 =
        21127033415818163472118538170889890527553241346229649624605221274417900855452;

    // Groth16 gamma point in G2 in powers of i
    uint256 constant GAMMA_NEG_X_0 =
        499394079463924875962540958234756143708429577458159326946111281188934993028;
    uint256 constant GAMMA_NEG_X_1 =
        21077090675402266397722954859415016654416356733606653058855662580372634814937;
    uint256 constant GAMMA_NEG_Y_0 =
        21079311319027403275557387062295699330658827889470830916149494859737623436894;
    uint256 constant GAMMA_NEG_Y_1 =
        17922207505503121146806922346995760187901822834219765373403832291996633569360;

    // Groth16 delta point in G2 in powers of i
    uint256 constant DELTA_NEG_X_0 =
        20287536507502928815755415033200397197241347462498607141501277090035751617946;
    uint256 constant DELTA_NEG_X_1 =
        20271679153223182686550786505203159428061009856639272167146423924973293551751;
    uint256 constant DELTA_NEG_Y_0 =
        4646539917572385376126580834872916886805974994211311635641582655361809245150;
    uint256 constant DELTA_NEG_Y_1 =
        8385798782071614272745500550950623661677313521612535590842438779503301227533;

    // Constant and public input points
    uint256 constant CONSTANT_X =
        12972316725243678057073554578436389425980338930515051194131416297164538120316;
    uint256 constant CONSTANT_Y =
        2467383993954176961469420008365922174939128553633466223895737162957587484853;
    uint256 constant PUB_0_X =
        13485612424243073354768837647639370850516262424802561696539968552274236968416;
    uint256 constant PUB_0_Y =
        16938032263268700169078617345222276997395080086877142413446124891951166401812;
    uint256 constant PUB_1_X =
        9577871516163645227022497024355368800947618780467796129245411052446525718871;
    uint256 constant PUB_1_Y =
        2255554114010427737405622218768298085159488821115248649813601084257761833871;
    uint256 constant PUB_2_X =
        2729903753053334408029284154588476418397822073389430137379814638748981364113;
    uint256 constant PUB_2_Y =
        8588614792037255042687646565980841920112454621626098637175634238186999562225;

    /// Negation in Fp.
    /// @notice Returns a number x such that a + x = 0 in Fp.
    /// @notice The input does not need to be reduced.
    /// @param a the base
    /// @return x the result
    function negate(uint256 a) internal pure returns (uint256 x) {
        unchecked {
            x = (P - (a % P)) % P; // Modulo is cheaper than branching
        }
    }

    /// Exponentiation in Fp.
    /// @notice Returns a number x such that a ^ e = x in Fp.
    /// @notice The input does not need to be reduced.
    /// @param a the base
    /// @param e the exponent
    /// @return x the result
    function exp(uint256 a, uint256 e) internal view returns (uint256 x) {
        bool success;
        assembly ("memory-safe") {
            let f := mload(0x40)
            mstore(f, 0x20)
            mstore(add(f, 0x20), 0x20)
            mstore(add(f, 0x40), 0x20)
            mstore(add(f, 0x60), a)
            mstore(add(f, 0x80), e)
            mstore(add(f, 0xa0), P)
            success := staticcall(gas(), PRECOMPILE_MODEXP, f, 0xc0, f, 0x20)
            x := mload(f)
        }
        if (!success) {
            // Exponentiation failed.
            // Should not happen.
            revert ProofInvalid();
        }
    }

    /// Invertsion in Fp.
    /// @notice Returns a number x such that a * x = 1 in Fp.
    /// @notice The input does not need to be reduced.
    /// @notice Reverts with ProofInvalid() if the inverse does not exist
    /// @param a the input
    /// @return x the solution
    function invert_Fp(uint256 a) internal view returns (uint256 x) {
        x = exp(a, EXP_INVERSE_FP);
        if (mulmod(a, x, P) != 1) {
            // Inverse does not exist.
            // Can only happen during G2 point decompression.
            revert ProofInvalid();
        }
    }

    /// Square root in Fp.
    /// @notice Returns a number x such that x * x = a in Fp.
    /// @notice Will revert with InvalidProof() if the input is not a square
    /// or not reduced.
    /// @param a the square
    /// @return x the solution
    function sqrt_Fp(uint256 a) internal view returns (uint256 x) {
        x = exp(a, EXP_SQRT_FP);
        if (mulmod(x, x, P) != a) {
            // Square root does not exist or a is not reduced.
            // Happens when G1 point is not on curve.
            revert ProofInvalid();
        }
    }

    /// Square test in Fp.
    /// @notice Returns wheter a number x exists such that x * x = a in Fp.
    /// @notice Will revert with InvalidProof() if the input is not a square
    /// or not reduced.
    /// @param a the square
    /// @return x the solution
    function isSquare_Fp(uint256 a) internal view returns (bool) {
        uint256 x = exp(a, EXP_SQRT_FP);
        return mulmod(x, x, P) == a;
    }

    /// Square root in Fp2.
    /// @notice Fp2 is the complex extension Fp[i]/(i^2 + 1). The input is
    /// a0 + a1 ⋅ i and the result is x0 + x1 ⋅ i.
    /// @notice Will revert with InvalidProof() if
    ///   * the input is not a square,
    ///   * the hint is incorrect, or
    ///   * the input coefficents are not reduced.
    /// @param a0 The real part of the input.
    /// @param a1 The imaginary part of the input.
    /// @param hint A hint which of two possible signs to pick in the equation.
    /// @return x0 The real part of the square root.
    /// @return x1 The imaginary part of the square root.
    function sqrt_Fp2(uint256 a0, uint256 a1, bool hint)
        internal
        view
        returns (uint256 x0, uint256 x1)
    {
        // If this square root reverts there is no solution in Fp2.
        uint256 d = sqrt_Fp(addmod(mulmod(a0, a0, P), mulmod(a1, a1, P), P));
        if (hint) {
            d = negate(d);
        }
        // If this square root reverts there is no solution in Fp2.
        x0 = sqrt_Fp(mulmod(addmod(a0, d, P), FRACTION_1_2_FP, P));
        x1 = mulmod(a1, invert_Fp(mulmod(x0, 2, P)), P);

        // Check result to make sure we found a root.
        // Note: this also fails if a0 or a1 is not reduced.
        if (
            a0 != addmod(mulmod(x0, x0, P), negate(mulmod(x1, x1, P)), P)
                || a1 != mulmod(2, mulmod(x0, x1, P), P)
        ) {
            revert ProofInvalid();
        }
    }

    /// Compress a G1 point.
    /// @notice Reverts with InvalidProof if the coordinates are not reduced
    /// or if the point is not on the curve.
    /// @notice The point at infinity is encoded as (0,0) and compressed to 0.
    /// @param x The X coordinate in Fp.
    /// @param y The Y coordinate in Fp.
    /// @return c The compresed point (x with one signal bit).
    function compress_g1(uint256 x, uint256 y)
        internal
        view
        returns (uint256 c)
    {
        if (x >= P || y >= P) {
            // G1 point not in field.
            revert ProofInvalid();
        }
        if (x == 0 && y == 0) {
            // Point at infinity
            return 0;
        }

        // Note: sqrt_Fp reverts if there is no solution, i.e. the x coordinate is invalid.
        uint256 y_pos = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P));
        if (y == y_pos) {
            return (x << 1) | 0;
        } else if (y == negate(y_pos)) {
            return (x << 1) | 1;
        } else {
            // G1 point not on curve.
            revert ProofInvalid();
        }
    }

    /// Decompress a G1 point.
    /// @notice Reverts with InvalidProof if the input does not represent a valid point.
    /// @notice The point at infinity is encoded as (0,0) and compressed to 0.
    /// @param c The compresed point (x with one signal bit).
    /// @return x The X coordinate in Fp.
    /// @return y The Y coordinate in Fp.
    function decompress_g1(uint256 c)
        internal
        view
        returns (uint256 x, uint256 y)
    {
        // Note that X = 0 is not on the curve since 0³ + 3 = 3 is not a square.
        // so we can use it to represent the point at infinity.
        if (c == 0) {
            // Point at infinity as encoded in EIP196 and EIP197.
            return (0, 0);
        }
        bool negate_point = c & 1 == 1;
        x = c >> 1;
        if (x >= P) {
            // G1 x coordinate not in field.
            revert ProofInvalid();
        }

        // Note: (x³ + 3) is irreducible in Fp, so it can not be zero and therefore
        //       y can not be zero.
        // Note: sqrt_Fp reverts if there is no solution, i.e. the point is not on the curve.
        y = sqrt_Fp(addmod(mulmod(mulmod(x, x, P), x, P), 3, P));
        if (negate_point) {
            y = negate(y);
        }
    }

    /// Compress a G2 point.
    /// @notice Reverts with InvalidProof if the coefficients are not reduced
    /// or if the point is not on the curve.
    /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1)
    /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i).
    /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0).
    /// @param x0 The real part of the X coordinate.
    /// @param x1 The imaginary poart of the X coordinate.
    /// @param y0 The real part of the Y coordinate.
    /// @param y1 The imaginary part of the Y coordinate.
    /// @return c0 The first half of the compresed point (x0 with two signal bits).
    /// @return c1 The second half of the compressed point (x1 unmodified).
    function compress_g2(uint256 x0, uint256 x1, uint256 y0, uint256 y1)
        internal
        view
        returns (uint256 c0, uint256 c1)
    {
        if (x0 >= P || x1 >= P || y0 >= P || y1 >= P) {
            // G2 point not in field.
            revert ProofInvalid();
        }
        if ((x0 | x1 | y0 | y1) == 0) {
            // Point at infinity
            return (0, 0);
        }

        // Compute y^2
        // Note: shadowing variables and scoping to avoid stack-to-deep.
        uint256 y0_pos;
        uint256 y1_pos;
        {
            uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P);
            uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P);
            uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P);
            y0_pos = addmod(
                FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P
            );
            y1_pos = negate(
                addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)
            );
        }

        // Determine hint bit
        // If this sqrt fails the x coordinate is not on the curve.
        bool hint;
        {
            uint256 d = sqrt_Fp(
                addmod(mulmod(y0_pos, y0_pos, P), mulmod(y1_pos, y1_pos, P), P)
            );
            hint =
                !isSquare_Fp(mulmod(addmod(y0_pos, d, P), FRACTION_1_2_FP, P));
        }

        // Recover y
        (y0_pos, y1_pos) = sqrt_Fp2(y0_pos, y1_pos, hint);
        if (y0 == y0_pos && y1 == y1_pos) {
            c0 = (x0 << 2) | (hint ? 2 : 0) | 0;
            c1 = x1;
        } else if (y0 == negate(y0_pos) && y1 == negate(y1_pos)) {
            c0 = (x0 << 2) | (hint ? 2 : 0) | 1;
            c1 = x1;
        } else {
            // G1 point not on curve.
            revert ProofInvalid();
        }
    }

    /// Decompress a G2 point.
    /// @notice Reverts with InvalidProof if the input does not represent a valid point.
    /// @notice The G2 curve is defined over the complex extension Fp[i]/(i^2 + 1)
    /// with coordinates (x0 + x1 ⋅ i, y0 + y1 ⋅ i).
    /// @notice The point at infinity is encoded as (0,0,0,0) and compressed to (0,0).
    /// @param c0 The first half of the compresed point (x0 with two signal bits).
    /// @param c1 The second half of the compressed point (x1 unmodified).
    /// @return x0 The real part of the X coordinate.
    /// @return x1 The imaginary poart of the X coordinate.
    /// @return y0 The real part of the Y coordinate.
    /// @return y1 The imaginary part of the Y coordinate.
    function decompress_g2(uint256 c0, uint256 c1)
        internal
        view
        returns (uint256 x0, uint256 x1, uint256 y0, uint256 y1)
    {
        // Note that X = (0, 0) is not on the curve since 0³ + 3/(9 + i) is not a square.
        // so we can use it to represent the point at infinity.
        if (c0 == 0 && c1 == 0) {
            // Point at infinity as encoded in EIP197.
            return (0, 0, 0, 0);
        }
        bool negate_point = c0 & 1 == 1;
        bool hint = c0 & 2 == 2;
        x0 = c0 >> 2;
        x1 = c1;
        if (x0 >= P || x1 >= P) {
            // G2 x0 or x1 coefficient not in field.
            revert ProofInvalid();
        }

        uint256 n3ab = mulmod(mulmod(x0, x1, P), P - 3, P);
        uint256 a_3 = mulmod(mulmod(x0, x0, P), x0, P);
        uint256 b_3 = mulmod(mulmod(x1, x1, P), x1, P);

        y0 = addmod(FRACTION_27_82_FP, addmod(a_3, mulmod(n3ab, x1, P), P), P);
        y1 = negate(
            addmod(FRACTION_3_82_FP, addmod(b_3, mulmod(n3ab, x0, P), P), P)
        );

        // Note: sqrt_Fp2 reverts if there is no solution, i.e. the point is not on the curve.
        // Note: (X³ + 3/(9 + i)) is irreducible in Fp2, so y can not be zero.
        //       But y0 or y1 may still independently be zero.
        (y0, y1) = sqrt_Fp2(y0, y1, hint);
        if (negate_point) {
            y0 = negate(y0);
            y1 = negate(y1);
        }
    }

    /// Compute the public input linear combination.
    /// @notice Reverts with PublicInputNotInField if the input is not in the field.
    /// @notice Computes the multi-scalar-multiplication of the public input
    /// elements and the verification key including the constant term.
    /// @param input The public inputs. These are elements of the scalar field Fr.
    /// @return x The X coordinate of the resulting G1 point.
    /// @return y The Y coordinate of the resulting G1 point.
    function publicInputMSM(uint256[3] memory input)
        internal
        view
        returns (uint256 x, uint256 y)
    {
        // Note: The ECMUL precompile does not reject unreduced values, so we check this.
        // Note: Unrolling this loop does not cost much extra in code-size, the bulk of the
        //       code-size is in the PUB_ constants.
        // ECMUL has input (x, y, scalar) and output (x', y').
        // ECADD has input (x1, y1, x2, y2) and output (x', y').
        // We call them such that ecmul output is already in the second point
        // argument to ECADD so we can have a tight loop.
        bool success = true;
        assembly ("memory-safe") {
            let f := mload(0x40)
            let g := add(f, 0x40)
            let s
            mstore(f, CONSTANT_X)
            mstore(add(f, 0x20), CONSTANT_Y)
            mstore(g, PUB_0_X)
            mstore(add(g, 0x20), PUB_0_Y)
            s := mload(input)
            mstore(add(g, 0x40), s)
            success := and(success, lt(s, R))
            success :=
                and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40))
            success :=
                and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40))
            mstore(g, PUB_1_X)
            mstore(add(g, 0x20), PUB_1_Y)
            s := mload(add(input, 32))
            mstore(add(g, 0x40), s)
            success := and(success, lt(s, R))
            success :=
                and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40))
            success :=
                and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40))
            mstore(g, PUB_2_X)
            mstore(add(g, 0x20), PUB_2_Y)
            s := mload(add(input, 64))
            mstore(add(g, 0x40), s)
            success := and(success, lt(s, R))
            success :=
                and(success, staticcall(gas(), PRECOMPILE_MUL, g, 0x60, g, 0x40))
            success :=
                and(success, staticcall(gas(), PRECOMPILE_ADD, f, 0x80, f, 0x40))
            x := mload(f)
            y := mload(add(f, 0x20))
        }
        if (!success) {
            // Either Public input not in field, or verification key invalid.
            // We assume the contract is correctly generated, so the verification key is valid.
            revert PublicInputNotInField();
        }
    }

    /// Compress a proof.
    /// @notice Will revert with InvalidProof if the curve points are invalid,
    /// but does not verify the proof itself.
    /// @param proof The uncompressed Groth16 proof. Elements are in the same order as for
    /// verifyProof. I.e. Groth16 points (A, B, C) encoded as in EIP-197.
    /// @return compressed The compressed proof. Elements are in the same order as for
    /// verifyCompressedProof. I.e. points (A, B, C) in compressed format.
    function compressProof(uint256[8] memory proof)
        internal
        view
        returns (uint256[4] memory compressed)
    {
        compressed[0] = compress_g1(proof[0], proof[1]);
        (compressed[2], compressed[1]) =
            compress_g2(proof[3], proof[2], proof[5], proof[4]);
        compressed[3] = compress_g1(proof[6], proof[7]);
    }

    /// Verify a Groth16 proof with compressed points.
    /// @notice Reverts with InvalidProof if the proof is invalid or
    /// with PublicInputNotInField the public input is not reduced.
    /// @notice There is no return value. If the function does not revert, the
    /// proof was successfully verified.
    /// @param compressedProof the points (A, B, C) in compressed format
    /// matching the output of compressProof.
    /// @param input the public input field elements in the scalar field Fr.
    /// Elements must be reduced.
    function verifyCompressedProof(
        uint256[4] calldata compressedProof,
        uint256[3] memory input
    ) internal view {
        (uint256 Ax, uint256 Ay) = decompress_g1(compressedProof[0]);
        (uint256 Bx0, uint256 Bx1, uint256 By0, uint256 By1) =
            decompress_g2(compressedProof[2], compressedProof[1]);
        (uint256 Cx, uint256 Cy) = decompress_g1(compressedProof[3]);
        (uint256 Lx, uint256 Ly) = publicInputMSM(input);

        // Verify the pairing
        // Note: The precompile expects the F2 coefficients in big-endian order.
        // Note: The pairing precompile rejects unreduced values, so we won't check that here.
        uint256[24] memory pairings;
        // e(A, B)
        pairings[0] = Ax;
        pairings[1] = Ay;
        pairings[2] = Bx1;
        pairings[3] = Bx0;
        pairings[4] = By1;
        pairings[5] = By0;
        // e(C, -δ)
        pairings[6] = Cx;
        pairings[7] = Cy;
        pairings[8] = DELTA_NEG_X_1;
        pairings[9] = DELTA_NEG_X_0;
        pairings[10] = DELTA_NEG_Y_1;
        pairings[11] = DELTA_NEG_Y_0;
        // e(α, -β)
        pairings[12] = ALPHA_X;
        pairings[13] = ALPHA_Y;
        pairings[14] = BETA_NEG_X_1;
        pairings[15] = BETA_NEG_X_0;
        pairings[16] = BETA_NEG_Y_1;
        pairings[17] = BETA_NEG_Y_0;
        // e(L_pub, -γ)
        pairings[18] = Lx;
        pairings[19] = Ly;
        pairings[20] = GAMMA_NEG_X_1;
        pairings[21] = GAMMA_NEG_X_0;
        pairings[22] = GAMMA_NEG_Y_1;
        pairings[23] = GAMMA_NEG_Y_0;

        // Check pairing equation.
        bool success;
        uint256[1] memory output;
        assembly ("memory-safe") {
            success :=
                staticcall(gas(), PRECOMPILE_VERIFY, pairings, 0x300, output, 0x20)
        }
        if (!success || output[0] != 1) {
            // Either proof or verification key invalid.
            // We assume the contract is correctly generated, so the verification key is valid.
            revert ProofInvalid();
        }
    }

    /// Verify an uncompressed Groth16 proof.
    /// @notice Reverts with InvalidProof if the proof is invalid or
    /// with PublicInputNotInField the public input is not reduced.
    /// @notice There is no return value. If the function does not revert, the
    /// proof was successfully verified.
    /// @param proof the points (A, B, C) in EIP-197 format matching the output
    /// of compressProof.
    /// @param input the public input field elements in the scalar field Fr.
    /// Elements must be reduced.
    function verifyProof(uint256[8] memory proof, uint256[3] memory input)
        internal
        view
    {
        (uint256 x, uint256 y) = publicInputMSM(input);

        // Note: The precompile expects the F2 coefficients in big-endian order.
        // Note: The pairing precompile rejects unreduced values, so we won't check that here.

        bool success;
        assembly ("memory-safe") {
            let f := mload(0x40) // Free memory pointer.

            // Copy points (A, B, C) to memory. They are already in correct encoding.
            // This is pairing e(A, B) and G1 of e(C, -δ).
            mstore(f, mload(add(proof, 0x00)))
            mstore(add(f, 0x20), mload(add(proof, 0x20)))
            mstore(add(f, 0x40), mload(add(proof, 0x40)))
            mstore(add(f, 0x60), mload(add(proof, 0x60)))
            mstore(add(f, 0x80), mload(add(proof, 0x80)))
            mstore(add(f, 0xa0), mload(add(proof, 0xa0)))
            mstore(add(f, 0xc0), mload(add(proof, 0xc0)))
            mstore(add(f, 0xe0), mload(add(proof, 0xe0)))

            // Complete e(C, -δ) and write e(α, -β), e(L_pub, -γ) to memory.
            // OPT: This could be better done using a single codecopy, but
            //      Solidity (unlike standalone Yul) doesn't provide a way to
            //      to do this.
            mstore(add(f, 0x100), DELTA_NEG_X_1)
            mstore(add(f, 0x120), DELTA_NEG_X_0)
            mstore(add(f, 0x140), DELTA_NEG_Y_1)
            mstore(add(f, 0x160), DELTA_NEG_Y_0)
            mstore(add(f, 0x180), ALPHA_X)
            mstore(add(f, 0x1a0), ALPHA_Y)
            mstore(add(f, 0x1c0), BETA_NEG_X_1)
            mstore(add(f, 0x1e0), BETA_NEG_X_0)
            mstore(add(f, 0x200), BETA_NEG_Y_1)
            mstore(add(f, 0x220), BETA_NEG_Y_0)
            mstore(add(f, 0x240), x)
            mstore(add(f, 0x260), y)
            mstore(add(f, 0x280), GAMMA_NEG_X_1)
            mstore(add(f, 0x2a0), GAMMA_NEG_X_0)
            mstore(add(f, 0x2c0), GAMMA_NEG_Y_1)
            mstore(add(f, 0x2e0), GAMMA_NEG_Y_0)

            // Check pairing equation.
            success := staticcall(gas(), PRECOMPILE_VERIFY, f, 0x300, f, 0x20)
            // Also check returned value (both are either 1 or 0).
            success := and(success, mload(f))
        }
        if (!success) {
            // Either proof or verification key invalid.
            // We assume the contract is correctly generated, so the verification key is valid.
            revert ProofInvalid();
        }
    }

    bytes32 constant CIRCUIT_DIGEST =
        0x299431a9262b45ab4993c36cd23c4cf37af4a66eb6207c49202a7c4176c60fbf;
}

// src/interfaces/ILPNRegistry.sol

/// @title ILPNRegistry
/// @notice Interface for the LPNRegistryV0 contract.
interface ILPNRegistry {
    /// @notice Event emitted when a new client registers.
    /// @param storageContract The address of the smart contract to be indexed.
    /// @param client The address of the client who requested this contract to be indexed.
    /// @param mappingSlot The storage slot of the client's mapping to be computed and proved over.
    /// @param lengthSlot The storage slot of the variable storing the length of the client's mapping.
    event NewRegistration(
        address indexed storageContract,
        address indexed client,
        uint256 mappingSlot,
        uint256 lengthSlot
    );

    /// @notice Event emitted when a new request is made.
    /// @param requestId The ID of the request.
    /// @param storageContract The address of the smart contract with the storage associated with the request.
    /// @param client The address of the client who made this request.
    /// @param params The query params associated with this query type.
    /// @param startBlock The starting block for the computation.
    /// @param endBlock The ending block for the computation.
    /// @param proofBlock The requested block for the proof to be computed against.
    ///                   Currently required for OP Stack chains
    event NewRequest(
        uint256 indexed requestId,
        address indexed storageContract,
        address indexed client,
        bytes32 params,
        uint256 startBlock,
        uint256 endBlock,
        uint256 offset,
        uint256 gasFee,
        uint256 proofBlock
    );

    /// @notice Event emitted when a response is received.
    /// @param requestId The ID of the request.
    /// @param client The address of the client who made the matching request.
    /// @param results The computed results for the request.
    event NewResponse(
        uint256 indexed requestId, address indexed client, uint256[] results
    );

    /// @notice The gas fee paid for on request to reimburse the response transaction.
    function gasFee() external returns (uint256);

    /// @notice Registers a client with the provided mapping and length slots.
    /// @param storageContract The address of the contract to be queried.
    /// @param mappingSlot The storage slot of the client's mapping to be computed and proved over.
    /// @param lengthSlot The storage slot of the variable storing the length of the client's mapping.
    function register(
        address storageContract,
        uint256 mappingSlot,
        uint256 lengthSlot
    ) external;

    /// @notice Submits a new request to the registry.
    /// @param storageContract The address of the smart contract with the storage associated with the request.
    /// @param params The query params associated with this query type.
    /// @param startBlock The starting block for the computation.
    /// @param endBlock The ending block for the computation.
    /// @return The ID of the newly created request.
    function request(
        address storageContract,
        bytes32 params,
        uint256 startBlock,
        uint256 endBlock
    ) external payable returns (uint256);

    /// @notice Submits a response to a specific request.
    /// @param requestId_ The ID of the request to respond to.
    /// @param data The proof, inputs, and public inputs to verify.
    /// - groth16_proof.proofs: 8 * U256 = 256 bytes
    /// - groth16_proof.inputs: 3 * U256 = 96 bytes
    /// - plonky2_proof.public_inputs: the little-endian bytes of public inputs exported by user
    /// @param blockNumber The block number of the block hash corresponding to the proof.
    function respond(
        uint256 requestId_,
        bytes32[] calldata data,
        uint256 blockNumber
    ) external;
}

// src/Groth16VerifierExtensions.sol

library Groth16VerifierExtensions {
    // byteLen(uint160) / 4
    uint32 constant PACKED_ADDRESS_LEN = 5;

    // byteLen(bytes32) / 4
    uint32 constant PACKED_HASH_LEN = 8;

    // byteLen(uint256) / 4
    uint32 constant PACKED_U256_LEN = 8;

    // Top 3 bits mask.
    uint256 constant TOP_THREE_BIT_MASK = ~(uint256(7) << 253);

    // Set the number of the NFT IDs. Each ID is an uint32.
    uint32 constant L = 5;

    // The start bytes32 offset of plonky2 public inputs in the whole data.
    // groth16_proof_number (8) + groth16_input_number (3)
    uint32 constant PLONKY2_PI_BYTES32_OFFSET = 11;

    // The total length of the plonky2 public inputs. Each input value is
    // serialized as an uint64. It's related with both the full proof
    // serialization and the wrapped circuit code.
    uint32 constant PI_TOTAL_LEN = (L + 41) * 8;

    // The min block number offset in the plonky2 public inputs.
    uint32 constant PI_MIN_BLOCK_NUM_OFFSET = 2 * 8;

    // The max block number offset in the plonky2 public inputs.
    uint32 constant PI_MAX_BLOCK_NUM_OFFSET = PI_MIN_BLOCK_NUM_OFFSET + 8;

    // The contract address offset in the plonky2 public inputs.
    uint32 constant PI_CONTRACT_ADDR_OFFSET = PI_MAX_BLOCK_NUM_OFFSET + 8;

    // The user address offset in the plonky2 public inputs.
    uint32 constant PI_USER_ADDR_OFFSET =
        PI_CONTRACT_ADDR_OFFSET + PACKED_ADDRESS_LEN * 8;

    // The NFT IDS offset in the plonky2 public inputs.
    uint32 constant PI_NFT_IDS_OFFSET = 16 * 8;

    // The block hash offset in the plonky2 public inputs.
    uint32 constant PI_BLOCK_HASH_OFFSET = PI_NFT_IDS_OFFSET + L * 8;

    // The rewards rate offset in the plonky2 public inputs.
    uint32 constant PI_REWARDS_RATE_OFFSET =
        PI_BLOCK_HASH_OFFSET + PACKED_U256_LEN * 8;

    // The ERC20 result offset in the plonky2 public inputs.
    uint32 constant PI_ERC20_RESULT_OFFSET =
        PI_REWARDS_RATE_OFFSET + PACKED_U256_LEN * 8;

    // The query identifier offset in the plonky2 public inputs.
    uint32 constant PI_QUERY_IDENTIFIER_OFFSET =
        PI_ERC20_RESULT_OFFSET + PACKED_U256_LEN * 8;

    // Supported query identifiers
    uint8 constant QUERY_IDENTIFIER_NFT = 67;
    uint8 constant QUERY_IDENTIFIER_ERC20 = 88;

    // The query struct used to check with the public inputs.
    struct Query {
        address contractAddress;
        uint96 minBlockNumber;
        address userAddress;
        uint96 maxBlockNumber;
        address clientAddress;
        uint88 rewardsRate;
        uint8 identifier;
        bytes32 blockHash;
    }

    // This processQuery function does the followings:
    // 1. Parse the Groth16 proofs (8 uint256) and inputs (3 uint256) from the `data` argument, and
    //    call `verifyProof` function for Groth16 verification.
    // 2. Parse the plonky2 public inputs from the `data` argument.
    // 3. Calculate sha256 on the inputs to a hash value, and set the top 3 bits of this hash to 0.
    //    Then asset this hash value must be equal to the last Groth16 input (groth16_inputs[2]).
    // 4. Parse a Query instance from the plonky2 public inputs, and asset it must be equal to the
    //    expected `query` argument.
    // 5. Parse and return the query result from the plonky2 public inputs.
    function processQuery(bytes32[] calldata data, Query memory query)
        internal
        view
        returns (uint256[] memory)
    {
        // 1. Do Groth16 verification.
        uint256[3] memory groth16_inputs = verifyGroth16Proof(data);

        // 2. Parse the plonky2 public inputs.
        bytes memory pis = parsePlonky2Inputs(data);

        // 3. Ensure the hash of plonky2 public inputs must be equal to the last Groth16 input.
        verifyPlonky2Inputs(pis, groth16_inputs);

        // 4. Asset the query in plonky2 public inputs must be equal to expected `query` argument.
        verifyQuery(pis, query);

        // 5. Parse and return the query result.
        return parseQueryResult(pis, query.identifier);
    }

    // Parse the Groth16 proofs and inputs, and do verification. It returns the Groth16 inputs.
    function verifyGroth16Proof(bytes32[] calldata data)
        internal
        view
        returns (uint256[3] memory)
    {
        uint256[8] memory proofs;
        uint256[3] memory inputs;

        for (uint32 i = 0; i < 8; ++i) {
            proofs[i] = convertBytes32ToU256(data[i]);
        }
        for (uint32 i = 0; i < 3; ++i) {
            inputs[i] = convertBytes32ToU256(data[i + 8]);
        }

        // Require the sha256 hash equals to the last Groth16 input.
        require(
            inputs[0] == uint256(Groth16Verifier.CIRCUIT_DIGEST),
            "The first Groth16 input must be equal to the circuit digest"
        );

        // Do Groth16 verification.
        Groth16Verifier.verifyProof(proofs, inputs);

        return inputs;
    }

    // Parse the plonky2 public inputs.
    function parsePlonky2Inputs(bytes32[] calldata data)
        internal
        pure
        returns (bytes memory)
    {
        bytes memory pis = new bytes(PI_TOTAL_LEN);

        uint32 bytes32_len = PI_TOTAL_LEN / 32;
        for (uint32 i = 0; i < bytes32_len; ++i) {
            bytes32 b = data[PLONKY2_PI_BYTES32_OFFSET + i];
            for (uint32 j = 0; j < 32; ++j) {
                pis[i * 32 + j] = bytes1(b[j]);
            }
        }

        // Set the remaining bytes.
        bytes32 remaining_data = data[PLONKY2_PI_BYTES32_OFFSET + bytes32_len];
        for (uint32 i = 0; i < PI_TOTAL_LEN % 32; ++i) {
            pis[bytes32_len * 32 + i] = remaining_data[i];
        }

        return pis;
    }

    // Calculate sha256 on the plonky2 inputs, and asset it must be equal to the last Groth16 input.
    function verifyPlonky2Inputs(
        bytes memory pis,
        uint256[3] memory groth16_inputs
    ) internal pure {
        // Calculate sha256.
        bytes32 pis_hash_bytes = sha256(pis);
        uint256 pis_hash = uint256(pis_hash_bytes);

        // Set the top 3 bits of the hash value to 0.
        pis_hash = pis_hash & TOP_THREE_BIT_MASK;

        // Require the sha256 hash equals to the last Groth16 input.
        require(
            pis_hash == groth16_inputs[2],
            "The plonky2 public inputs hash must be equal to the last of the Groth16 inputs"
        );
    }

    // Verify the plonky2 inputs with the expected Query instance.
    function verifyQuery(bytes memory pis, Query memory query) internal pure {
        uint32 minBlockNumber = convertToU32(pis, PI_MIN_BLOCK_NUM_OFFSET);
        require(
            minBlockNumber == query.minBlockNumber,
            "The parsed min block number must be equal to the expected one in query."
        );

        uint32 maxBlockNumber = convertToU32(pis, PI_MAX_BLOCK_NUM_OFFSET);
        require(
            maxBlockNumber == query.maxBlockNumber,
            "The parsed max block number must be equal to the expected one in query."
        );

        address contractAddress = convertToAddress(pis, PI_CONTRACT_ADDR_OFFSET);
        require(
            contractAddress == query.contractAddress,
            "The parsed contract address must be equal to the expected one in query."
        );

        address userAddress = convertToAddress(pis, PI_USER_ADDR_OFFSET);
        require(
            userAddress == query.userAddress,
            "The parsed user address must be equal to the expected one in query."
        );

        bytes32 blockHash = bytes32(convertToHash(pis, PI_BLOCK_HASH_OFFSET));
        require(
            blockHash == query.blockHash,
            "The parsed block hash must be equal to the expected one in query."
        );

        if (query.identifier == QUERY_IDENTIFIER_ERC20) {
            uint256 rewardsRate =
                convertByteSliceToU256(pis, PI_REWARDS_RATE_OFFSET);
            require(
                rewardsRate == query.rewardsRate,
                "The parsed rewards rate must be equal to the expected one in query."
            );
        }

        require(
            uint8(pis[PI_QUERY_IDENTIFIER_OFFSET]) == query.identifier,
            "The parsed identifier must be equal to the expected one in query."
        );
    }

    // Parse the query result from the plonky2 public inputs.
    function parseQueryResult(bytes memory pis, uint8 identifier)
        internal
        pure
        returns (uint256[] memory)
    {
        if (identifier == QUERY_IDENTIFIER_NFT) {
            return parseNftIds(pis);
        } else if (identifier == QUERY_IDENTIFIER_ERC20) {
            return parseErc20Result(pis);
        } else {
            revert("Unsupported query identifier");
        }
    }

    // Parse the `L` NFT IDs from the plonky2 public inputs.
    function parseNftIds(bytes memory pis)
        internal
        pure
        returns (uint256[] memory)
    {
        uint256[] memory nft_ids = new uint256[](L);
        for (uint32 i = 0; i < L; ++i) {
            nft_ids[i] =
                uint256(convertToLeftPaddingU32(pis, PI_NFT_IDS_OFFSET + i * 8));
        }

        return nft_ids;
    }

    // Parse the ERC20 result from the plonky2 public inputs.
    function parseErc20Result(bytes memory pis)
        internal
        pure
        returns (uint256[] memory)
    {
        uint256[] memory result = new uint256[](1);
        result[0] = convertByteSliceToU256(pis, PI_ERC20_RESULT_OFFSET);

        return result;
    }

    // Convert to an uint32 from a memory offset.
    function convertToU32(bytes memory data, uint32 offset)
        internal
        pure
        returns (uint32)
    {
        uint32 result;
        for (uint32 i = 0; i < 4; ++i) {
            result |= uint32(uint8(data[i + offset])) << (8 * i);
        }

        return result;
    }

    // Convert to an uint32 of left padding from a memory offset.
    function convertToLeftPaddingU32(bytes memory data, uint32 offset)
        internal
        pure
        returns (uint32)
    {
        uint32 result;
        for (uint32 i = 0; i < 4; ++i) {
            result |= uint32(uint8(data[i + offset])) << (8 * (3 - i));
        }

        return result;
    }

    // Convert a bytes32 to an uint256.
    function convertBytes32ToU256(bytes32 b) internal pure returns (uint256) {
        uint256 result;
        for (uint32 i = 0; i < 32; i++) {
            result |= uint256(uint8(b[i])) << (8 * i);
        }

        return result;
    }

    // Convert the specified byte slice to an uint256.
    function convertByteSliceToU256(bytes memory pis, uint32 offset)
        internal
        pure
        returns (uint256)
    {
        uint256 result;
        for (uint32 i = 0; i < 8; ++i) {
            result |= uint256(convertToU32(pis, offset + i * 8)) << (32 * i);
        }

        return result;
    }

    // Convert to an address from a memory offset.
    function convertToAddress(bytes memory pis, uint32 offset)
        internal
        pure
        returns (address)
    {
        uint160 result;
        for (uint32 i = 0; i < PACKED_ADDRESS_LEN; ++i) {
            result |= uint160(convertToLeftPaddingU32(pis, offset + i * 8))
                << (32 * (PACKED_ADDRESS_LEN - 1 - i));
        }

        return address(result);
    }

    // Convert to a hash from a memory offset.
    function convertToHash(bytes memory pis, uint32 offset)
        internal
        pure
        returns (bytes32)
    {
        uint256 result;
        for (uint32 i = 0; i < PACKED_HASH_LEN; ++i) {
            result |= uint256(convertToLeftPaddingU32(pis, offset + i * 8))
                << (32 * (PACKED_HASH_LEN - 1 - i));
        }

        return bytes32(result);
    }
}

// src/interfaces/ILPNClient.sol

/**
 * @title ILPNClient
 * @notice Interface for the LPNClientV0 contract.
 */
interface ILPNClient {
    /// @notice Callback function called by the LPNRegistry contract.
    /// @param requestId The ID of the request.
    /// @param results The result of the request.
    function lpnCallback(uint256 requestId, uint256[] calldata results)
        external;
}

// src/client/LPNClientV0.sol

error CallbackNotAuthorized();

abstract contract LPNClientV0 is ILPNClient {
    ILPNRegistry public lpnRegistry;

    modifier onlyLagrangeRegistry() {
        if (msg.sender != address(lpnRegistry)) {
            revert CallbackNotAuthorized();
        }
        _;
    }

    constructor(ILPNRegistry _lpnRegistry) {
        lpnRegistry = _lpnRegistry;
    }

    function lpnCallback(uint256 requestId, uint256[] calldata results)
        external
        onlyLagrangeRegistry
    {
        processCallback(requestId, results);
    }

    function processCallback(uint256 requestId, uint256[] calldata results)
        internal
        virtual;
}

// src/utils/QueryParams.sol

uint8 constant NFT_QUERY_IDENTIFIER =
    uint8(Groth16VerifierExtensions.QUERY_IDENTIFIER_NFT);

uint8 constant ERC20_QUERY_IDENTIFIER =
    uint8(Groth16VerifierExtensions.QUERY_IDENTIFIER_ERC20);

/// @notice Error thrown when specifying params with an unknown query identifier.
error UnsupportedParams();

/// @title Helper lib for constructing params to queries
library QueryParams {
    /// @notice Calldata parameters for an NFT Query
    /// @param identifier The identifier for the query type
    /// @param userAddress The address of the user associated with the query
    /// @param offset The offset value for pagination or data fetching
    struct NFTQueryParams {
        uint8 identifier;
        address userAddress;
        uint88 offset;
    }

    /// @notice Calldata parameters for an ERC20 Query
    /// @param identifier The identifier for the query type
    /// @param userAddress The address of the user associated with the query
    /// @param rewardsRate The rewards rate for the ERC20 token
    struct ERC20QueryParams {
        uint8 identifier;
        address userAddress;
        uint88 rewardsRate;
    }

    /// @notice Combined structure of all possible query parameters
    /// @param identifier The identifier for the query type
    /// @param userAddress The address of the user associated with the query
    /// @param rewardsRate The rewards rate for the ERC20 token
    /// @param offset The offset value for pagination or data fetching
    struct CombinedParams {
        uint8 identifier;
        address userAddress;
        uint88 rewardsRate;
        uint256 offset;
    }

    function newNFTQueryParams(address userAddress, uint88 offset)
        internal
        pure
        returns (NFTQueryParams memory)
    {
        return NFTQueryParams(NFT_QUERY_IDENTIFIER, userAddress, offset);
    }

    function newERC20QueryParams(address userAddress, uint88 rewardsRate)
        internal
        pure
        returns (ERC20QueryParams memory)
    {
        return
            ERC20QueryParams(ERC20_QUERY_IDENTIFIER, userAddress, rewardsRate);
    }

    function toBytes32(NFTQueryParams memory params)
        internal
        pure
        returns (bytes32)
    {
        return bytes32(
            uint256(params.identifier) << 248
                | uint256(uint160(params.userAddress)) << 88
                | uint256(params.offset)
        );
    }

    function toBytes32(ERC20QueryParams memory params)
        internal
        pure
        returns (bytes32)
    {
        return bytes32(
            uint256(params.identifier) << 248
                | uint256(uint160(params.userAddress)) << 88
                | uint256(params.rewardsRate)
        );
    }

    function fromBytes32(NFTQueryParams memory, bytes32 params)
        internal
        pure
        returns (NFTQueryParams memory)
    {
        uint8 identifier = uint8(uint256(params) >> 248);
        address userAddress = address(uint160(uint256(params) >> 88));
        uint88 offset = uint88(uint256(params));
        return NFTQueryParams(identifier, userAddress, offset);
    }

    function fromBytes32(ERC20QueryParams memory, bytes32 params)
        internal
        pure
        returns (ERC20QueryParams memory)
    {
        uint8 identifier = uint8(uint256(params) >> 248);
        address userAddress = address(uint160(uint256(params) >> 88));
        uint88 rewardsRate = uint88(uint256(params));
        return ERC20QueryParams(identifier, userAddress, rewardsRate);
    }

    /// @notice Parse structured values from 32 bytes of params
    /// @param params 32-bytes of abi-encoded params values
    function combinedFromBytes32(bytes32 params)
        internal
        pure
        returns (CombinedParams memory)
    {
        CombinedParams memory cp = CombinedParams({
            identifier: uint8(bytes1(params[0])),
            userAddress: address(0),
            rewardsRate: uint88(0),
            offset: uint88(0)
        });

        if (cp.identifier == NFT_QUERY_IDENTIFIER) {
            NFTQueryParams memory p;
            p = fromBytes32(p, params);

            cp.userAddress = p.userAddress;
            cp.offset = p.offset;

            return cp;
        }

        if (cp.identifier == ERC20_QUERY_IDENTIFIER) {
            ERC20QueryParams memory p;
            p = fromBytes32(p, params);

            cp.userAddress = p.userAddress;
            cp.rewardsRate = p.rewardsRate;

            return cp;
        }

        revert UnsupportedParams();
    }
}

// src/client/LPNQueryV0.sol

/**
 * @title LPNQueryV0
 * @dev A contract for querying NFT ownership using the Lagrange Euclid testnet.
 */
contract LPNQueryV0 is LPNClientV0 {
    using QueryParams for QueryParams.NFTQueryParams;
    using QueryParams for QueryParams.ERC20QueryParams;

    /**
     * @dev Struct to store metadata about a query request.
     * @param sender The address that sent the query request.
     * @param holder The address of the NFT holder being queried.
     */
    struct RequestMetadata {
        address sender;
        address holder;
    }

    /**
     * @dev Mapping to store request metadata by request ID.
     */
    mapping(uint256 requestId => RequestMetadata request) public requests;

    /**
     * @dev Event emitted when a query request is made.
     * @param sender The address that sent the query request.
     * @param storageContract The address of the NFT contract being queried.
     */
    event Query(address indexed sender, address indexed storageContract);

    /**
     * @dev Event emitted when the result of a query is received.
     * @param requestId The ID of the query request.
     * @param sender The address that sent the query request.
     * @param holder The address of the NFT holder that was queried.
     * @param results The array of NFT IDs owned by the queried holder.
     */
    event Result(
        uint256 indexed requestId,
        address indexed sender,
        address indexed holder,
        uint256[] results
    );

    /**
     * @dev Constructor to initialize the LPNQueryV0 contract.
     * @param lpnRegistry The address of the LPN registry contract.
     */
    constructor(ILPNRegistry lpnRegistry) LPNClientV0(lpnRegistry) {}

    /**
     * @dev Function to query the NFT IDs of a specific owner over a range of blocks.
     * @param storageContract The address of the NFT contract to query.
     * @param holder The address of the NFT holder to query.
     * @param startBlock The starting block number for the query range.
     * @param endBlock The ending block number for the query range.
     * @param offset The offset for pagination of results.
     */
    function queryNFT(
        address storageContract,
        address holder,
        uint256 startBlock,
        uint256 endBlock,
        uint88 offset
    ) external payable {
        uint256 requestId = lpnRegistry.request{value: msg.value}(
            storageContract,
            QueryParams.newNFTQueryParams(holder, offset).toBytes32(),
            startBlock,
            endBlock
        );

        requests[requestId] =
            RequestMetadata({sender: msg.sender, holder: holder});

        emit Query(msg.sender, storageContract);
    }

    /**
     * @dev Function to query the proportionate erc20 balance of a specific token holder over a range of blocks.
     * @param storageContract The address of the NFT contract to query.
     * @param holder The address of the NFT holder to query.
     * @param startBlock The starting block number for the query range.
     * @param endBlock The ending block number for the query range.
     * @param rewardsRate The multiplier to apply for e.g. calculating rewards.
     */
    function queryERC20(
        address storageContract,
        address holder,
        uint256 startBlock,
        uint256 endBlock,
        uint88 rewardsRate
    ) external payable {
        uint256 requestId = lpnRegistry.request{value: msg.value}(
            storageContract,
            QueryParams.newERC20QueryParams(holder, rewardsRate).toBytes32(),
            startBlock,
            endBlock
        );

        requests[requestId] =
            RequestMetadata({sender: msg.sender, holder: holder});

        emit Query(msg.sender, storageContract);
    }

    /**
     * @dev Internal function called by LPNClientV0 to provide the result of a query.
     * @param requestId The ID of the query request.
     * @param results The array of NFT IDs owned by the queried holder.
     */
    function processCallback(uint256 requestId, uint256[] calldata results)
        internal
        override
    {
        RequestMetadata memory req = requests[requestId];
        emit Result(requestId, req.sender, req.holder, results);
        delete requests[requestId];
    }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"contract ILPNRegistry","name":"lpnRegistry","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"CallbackNotAuthorized","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"storageContract","type":"address"}],"name":"Query","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"requestId","type":"uint256"},{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"holder","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"results","type":"uint256[]"}],"name":"Result","type":"event"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"},{"internalType":"uint256[]","name":"results","type":"uint256[]"}],"name":"lpnCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lpnRegistry","outputs":[{"internalType":"contract ILPNRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"storageContract","type":"address"},{"internalType":"address","name":"holder","type":"address"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"uint88","name":"rewardsRate","type":"uint88"}],"name":"queryERC20","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"storageContract","type":"address"},{"internalType":"address","name":"holder","type":"address"},{"internalType":"uint256","name":"startBlock","type":"uint256"},{"internalType":"uint256","name":"endBlock","type":"uint256"},{"internalType":"uint88","name":"offset","type":"uint88"}],"name":"queryNFT","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"requestId","type":"uint256"}],"name":"requests","outputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"holder","type":"address"}],"stateMutability":"view","type":"function"}]

6080604052348015600e575f80fd5b50604051610628380380610628833981016040819052602b91604e565b5f80546001600160a01b0319166001600160a01b03929092169190911790556079565b5f60208284031215605d575f80fd5b81516001600160a01b03811681146072575f80fd5b9392505050565b6105a2806100865f395ff3fe608060405260043610610049575f3560e01c80633e8258f11461004d5780636337ec5a1461006e57806381d12c58146100a9578063de72e30214610108578063f0ce5ce11461011b575b5f80fd5b348015610058575f80fd5b5061006c610067366004610412565b61012e565b005b348015610079575f80fd5b505f5461008c906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100b4575f80fd5b506100e86100c336600461048a565b600160208190525f918252604090912080549101546001600160a01b03918216911682565b604080516001600160a01b039384168152929091166020830152016100a0565b61006c6101163660046104bc565b610168565b61006c6101293660046104bc565b61030b565b5f546001600160a01b03163314610158576040516311016bf360e01b815260040160405180910390fd5b61016383838361036f565b505050565b5f80546001600160a01b0316638632627e34886102036101cc8a8860408051606080820183525f80835260208084018290529284015282519081018352604381526001600160a01b0394909416908401526001600160581b03919091169082015290565b5f81604001516001600160581b0316605883602001516001600160a01b0316901b60f8845f015160ff16901b17175f1b9050919050565b6040516001600160e01b031960e086901b1681526001600160a01b0390921660048301526024820152604481018890526064810187905260840160206040518083038185885af1158015610259573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061027e919061051e565b604080518082018252338082526001600160a01b0389811660208085019182525f878152600191829052868120955186549085166001600160a01b03199182161787559251959091018054958416959092169490941790559251939450918916927f5904592568a4fbb78135ee4f11eab3d6638454d85eec4b0f4e1002f125fd66b29190a3505050505050565b5f80546001600160a01b0316638632627e34886102036101cc8a8860408051606080820183525f80835260208084018290529284015282519081018352605881526001600160a01b0394909416908401526001600160581b03919091169082015290565b5f838152600160208181526040928390208351808501855281546001600160a01b03908116808352929094015490931691830182905292519192909186907f96cd981e2886f10a0f0fdfa65634c1242f211bf9abdbaaf18e199aed945b6ff9906103dc9088908890610535565b60405180910390a45050505f90815260016020819052604090912080546001600160a01b03199081168255910180549091169055565b5f805f60408486031215610424575f80fd5b83359250602084013567ffffffffffffffff80821115610442575f80fd5b818601915086601f830112610455575f80fd5b813581811115610463575f80fd5b8760208260051b8501011115610477575f80fd5b6020830194508093505050509250925092565b5f6020828403121561049a575f80fd5b5035919050565b80356001600160a01b03811681146104b7575f80fd5b919050565b5f805f805f60a086880312156104d0575f80fd5b6104d9866104a1565b94506104e7602087016104a1565b9350604086013592506060860135915060808601356001600160581b0381168114610510575f80fd5b809150509295509295909350565b5f6020828403121561052e575f80fd5b5051919050565b602080825281018290525f6001600160fb1b03831115610553575f80fd5b8260051b8085604085013791909101604001939250505056fea2646970667358221220a3285985890ef12078da18e3d3bb7bdf7659eb71c387608473d3257018a1affa64736f6c634300081900330000000000000000000000002584665beff871534118aabae781bc267af142f9

Deployed Bytecode

0x608060405260043610610049575f3560e01c80633e8258f11461004d5780636337ec5a1461006e57806381d12c58146100a9578063de72e30214610108578063f0ce5ce11461011b575b5f80fd5b348015610058575f80fd5b5061006c610067366004610412565b61012e565b005b348015610079575f80fd5b505f5461008c906001600160a01b031681565b6040516001600160a01b0390911681526020015b60405180910390f35b3480156100b4575f80fd5b506100e86100c336600461048a565b600160208190525f918252604090912080549101546001600160a01b03918216911682565b604080516001600160a01b039384168152929091166020830152016100a0565b61006c6101163660046104bc565b610168565b61006c6101293660046104bc565b61030b565b5f546001600160a01b03163314610158576040516311016bf360e01b815260040160405180910390fd5b61016383838361036f565b505050565b5f80546001600160a01b0316638632627e34886102036101cc8a8860408051606080820183525f80835260208084018290529284015282519081018352604381526001600160a01b0394909416908401526001600160581b03919091169082015290565b5f81604001516001600160581b0316605883602001516001600160a01b0316901b60f8845f015160ff16901b17175f1b9050919050565b6040516001600160e01b031960e086901b1681526001600160a01b0390921660048301526024820152604481018890526064810187905260840160206040518083038185885af1158015610259573d5f803e3d5ffd5b50505050506040513d601f19601f8201168201806040525081019061027e919061051e565b604080518082018252338082526001600160a01b0389811660208085019182525f878152600191829052868120955186549085166001600160a01b03199182161787559251959091018054958416959092169490941790559251939450918916927f5904592568a4fbb78135ee4f11eab3d6638454d85eec4b0f4e1002f125fd66b29190a3505050505050565b5f80546001600160a01b0316638632627e34886102036101cc8a8860408051606080820183525f80835260208084018290529284015282519081018352605881526001600160a01b0394909416908401526001600160581b03919091169082015290565b5f838152600160208181526040928390208351808501855281546001600160a01b03908116808352929094015490931691830182905292519192909186907f96cd981e2886f10a0f0fdfa65634c1242f211bf9abdbaaf18e199aed945b6ff9906103dc9088908890610535565b60405180910390a45050505f90815260016020819052604090912080546001600160a01b03199081168255910180549091169055565b5f805f60408486031215610424575f80fd5b83359250602084013567ffffffffffffffff80821115610442575f80fd5b818601915086601f830112610455575f80fd5b813581811115610463575f80fd5b8760208260051b8501011115610477575f80fd5b6020830194508093505050509250925092565b5f6020828403121561049a575f80fd5b5035919050565b80356001600160a01b03811681146104b7575f80fd5b919050565b5f805f805f60a086880312156104d0575f80fd5b6104d9866104a1565b94506104e7602087016104a1565b9350604086013592506060860135915060808601356001600160581b0381168114610510575f80fd5b809150509295509295909350565b5f6020828403121561052e575f80fd5b5051919050565b602080825281018290525f6001600160fb1b03831115610553575f80fd5b8260051b8085604085013791909101604001939250505056fea2646970667358221220a3285985890ef12078da18e3d3bb7bdf7659eb71c387608473d3257018a1affa64736f6c63430008190033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

0000000000000000000000002584665beff871534118aabae781bc267af142f9

-----Decoded View---------------
Arg [0] : lpnRegistry (address): 0x2584665Beff871534118aAbAE781BC267Af142f9

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 0000000000000000000000002584665beff871534118aabae781bc267af142f9


Deployed Bytecode Sourcemap

49138:4254:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;43995:175;;;;;;;;;;-1:-1:-1;43995:175:0;;;;;:::i;:::-;;:::i;:::-;;43696:31;;;;;;;;;;-1:-1:-1;43696:31:0;;;;-1:-1:-1;;;;;43696:31:0;;;;;;-1:-1:-1;;;;;887:32:1;;;869:51;;857:2;842:18;43696:31:0;;;;;;;;49672:69;;;;;;;;;;-1:-1:-1;49672:69:0;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;49672:69:0;;;;;;;;;;;-1:-1:-1;;;;;1346:15:1;;;1328:34;;1398:15;;;;1393:2;1378:18;;1371:43;1263:18;49672:69:0;1116:304:1;51212:571:0;;;;;;:::i;:::-;;:::i;52281:585::-;;;;;;:::i;:::-;;:::i;43995:175::-;43805:11;;-1:-1:-1;;;;;43805:11:0;43783:10;:34;43779:97;;43841:23;;-1:-1:-1;;;43841:23:0;;;;;;;;;;;43779:97;44127:35:::1;44143:9;44154:7;;44127:15;:35::i;:::-;43995:175:::0;;;:::o;51212:571::-;51404:17;51424:11;;-1:-1:-1;;;;;51424:11:0;:19;51451:9;51476:15;51506:57;:45;51536:6;51544;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;46151:57:0;;;;;;;33387:2;46151:57;;-1:-1:-1;;;;;46151:57:0;;;;;;;;-1:-1:-1;;;;;46151:57:0;;;;;;;;;45992:224;51506:45;46586:7;46763:6;:13;;;-1:-1:-1;;;;;46755:22:0;46733:2;46709:6;:18;;;-1:-1:-1;;;;;46693:36:0;:42;;46670:3;46648:6;:17;;;46640:26;;:33;;:95;:137;46618:170;;46611:177;;46487:309;;;;51506:57;51424:198;;-1:-1:-1;;;;;;51424:198:0;;;;;;;-1:-1:-1;;;;;2433:32:1;;;51424:198:0;;;2415:51:1;2482:18;;;2475:34;2525:18;;;2518:34;;;2568:18;;;2561:34;;;2387:19;;51424:198:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;51670:53;;;;;;;;51695:10;51670:53;;;-1:-1:-1;;;;;51670:53:0;;;;;;;;;;-1:-1:-1;51635:19:0;;;:8;:19;;;;;;;:88;;;;;;;-1:-1:-1;;;;;;51635:88:0;;;;;;;;;;;;;;;;;;;;;;;;;;;51741:34;;51404:218;;-1:-1:-1;51741:34:0;;;;;;-1:-1:-1;51741:34:0;51393:390;51212:571;;;;;:::o;52281:585::-;52480:17;52500:11;;-1:-1:-1;;;;;52500:11:0;:19;52527:9;52552:15;52582:64;:52;52614:6;52622:11;-1:-1:-1;;;;;;;;;;;;;;;;;;;;;;;;46405:66:0;;;;;;;33436:2;46405:66;;-1:-1:-1;;;;;46405:66:0;;;;;;;;-1:-1:-1;;;;;46405:66:0;;;;;;;;;46224:255;53106:283;53230:26;53259:19;;;:8;:19;;;;;;;;;53230:48;;;;;;;;;-1:-1:-1;;;;;53230:48:0;;;;;;;;;;;;;;;;;;;;53294:50;;53230:48;;;;53268:9;;53294:50;;;;53336:7;;;;53294:50;:::i;:::-;;;;;;;;-1:-1:-1;;;53362:19:0;;;;:8;:19;;;;;;;;53355:26;;-1:-1:-1;;;;;;53355:26:0;;;;;;;;;;;;;;53106:283::o;14:683:1:-;109:6;117;125;178:2;166:9;157:7;153:23;149:32;146:52;;;194:1;191;184:12;146:52;230:9;217:23;207:33;;291:2;280:9;276:18;263:32;314:18;355:2;347:6;344:14;341:34;;;371:1;368;361:12;341:34;409:6;398:9;394:22;384:32;;454:7;447:4;443:2;439:13;435:27;425:55;;476:1;473;466:12;425:55;516:2;503:16;542:2;534:6;531:14;528:34;;;558:1;555;548:12;528:34;611:7;606:2;596:6;593:1;589:14;585:2;581:23;577:32;574:45;571:65;;;632:1;629;622:12;571:65;663:2;659;655:11;645:21;;685:6;675:16;;;;;14:683;;;;;:::o;931:180::-;990:6;1043:2;1031:9;1022:7;1018:23;1014:32;1011:52;;;1059:1;1056;1049:12;1011:52;-1:-1:-1;1082:23:1;;931:180;-1:-1:-1;931:180:1:o;1425:173::-;1493:20;;-1:-1:-1;;;;;1542:31:1;;1532:42;;1522:70;;1588:1;1585;1578:12;1522:70;1425:173;;;:::o;1603:576::-;1697:6;1705;1713;1721;1729;1782:3;1770:9;1761:7;1757:23;1753:33;1750:53;;;1799:1;1796;1789:12;1750:53;1822:29;1841:9;1822:29;:::i;:::-;1812:39;;1870:38;1904:2;1893:9;1889:18;1870:38;:::i;:::-;1860:48;;1955:2;1944:9;1940:18;1927:32;1917:42;;2006:2;1995:9;1991:18;1978:32;1968:42;;2060:3;2049:9;2045:19;2032:33;-1:-1:-1;;;;;2098:5:1;2094:36;2087:5;2084:47;2074:75;;2145:1;2142;2135:12;2074:75;2168:5;2158:15;;;1603:576;;;;;;;;:::o;2606:184::-;2676:6;2729:2;2717:9;2708:7;2704:23;2700:32;2697:52;;;2745:1;2742;2735:12;2697:52;-1:-1:-1;2768:16:1;;2606:184;-1:-1:-1;2606:184:1:o;2795:443::-;2984:2;2966:21;;;3003:18;;2996:34;;;-1:-1:-1;;;;;;3042:31:1;;3039:51;;;3086:1;3083;3076:12;3039:51;3120:6;3117:1;3113:14;3177:6;3169;3164:2;3153:9;3149:18;3136:48;3205:22;;;;3229:2;3201:31;;2795:443;-1:-1:-1;;;2795:443:1:o

Swarm Source

ipfs://a3285985890ef12078da18e3d3bb7bdf7659eb71c387608473d3257018a1affa

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
[ Download: CSV Export  ]
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.