7Block Labs
Technical

ByAUJay

From EIP-2537 to Production: Verifying BLS Signatures in Solidity Without Tears

Short version: The Ethereum Pectra upgrade on May 7, 2025, rolled out native BLS12-381 precompiles (thanks to EIP-2537), making on-chain BLS verification totally doable on mainnet. Below, you’ll find a hands-on guide to implementing BLS signatures and aggregates in Solidity on Ethereum (and L2s that align with Pectra). We’ve got the specific byte layouts, gas calculations, and code patterns that you can easily use in audits. (eips.ethereum.org)


Who should read this

  • Startup and enterprise leaders diving into BLS-backed wallets, cross-chain bridges, light clients, or validator tools.
  • Engineering managers looking for clear, detailed implementation specifics instead of vague reassurances like “BLS is fast now.”

What changed in 2025 (and why it matters)

  • Pectra (Prague × Electra) officially launched on the Ethereum mainnet on May 7, 2025 (in epoch 364032), and it’s exciting because it includes EIP-2537. This means you can now call BLS12-381 precompiles at specific addresses straight from your Solidity code, without needing to dive into those tricky big-integer libraries or rely on L2 workarounds. Plus, the testnet activation blocks and epochs are all clearly laid out in the meta-EIP. Check it out here: (eips.ethereum.org).
  • Back in March 2024, Cancun introduced the KZG point-evaluation precompile at 0x0A through EIP-4844. With Pectra in the mix, we’re completing the puzzle by adding general BLS12-381 operations for both signatures and pairings. This dynamic duo makes it possible to verify blob data consistency (thanks to KZG) and confirm your identity using BLS signatures--all in one seamless on-chain process. For more details, take a look at this link: (eips.ethereum.org).

Why You Should Care

  • Good news: you can now do on-chain verification of validator-style aggregate signatures in Solidity, and it comes with predictable gas costs! This opens up a whole new realm of possibilities for bridges, DA attestors, rollup sequencer committees, and multi-sig/threshold wallets. With BLS’s aggregation features, you can save on both costs and latency. Check it out here: (eips.ethereum.org)

The new precompiles you can actually call

EIP-2537 rolls out seven precompiles located at these addresses:

  • 0x0b: BLS12_G1ADD
  • 0x0c: BLS12_G1MSM (that’s multi-scalar multiplication for you)
  • 0x0d: BLS12_G2ADD
  • 0x0e: BLS12_G2MSM
  • 0x0f: BLS12_PAIRING_CHECK
  • 0x10: BLS12_MAP_FP_TO_G1
  • 0x11: BLS12_MAP_FP2_TO_G2 (eips.ethereum.org)

Key details you’ll actually need:

  • Pairing check gas: The formula is pretty straightforward: 37700 + 32600 × k, where k represents the number of (G1, G2) pairs you process. For a basic verification involving just 2 pairings, you’re looking at around 102,900 gas just for that pairing operation. So, make sure to plan your gas capacity accordingly. (eips.ethereum.org)
  • Encodings are big-endian and uncompressed:

    • Fp element: Takes up 64 bytes (just a heads-up--the top 16 bytes must be zero since p is 381 bits).
    • G1 point: This one’s 128 bytes and is structured as x(64) || y(64).
    • Fp2 element: Also 128 bytes, made up of c0(64) || c1(64).
    • G2 point: That’s a hefty 256 bytes, formatted as x(128) || y(128).
    • Infinity is represented by all zeros when it comes to point size. (eips.ethereum.org)
  • Subgroup checks:

    • It’s crucial that MSM and pairing operations perform subgroup checks.
    • On the flip side, G1ADD and G2ADD do NOT carry out these checks. So, be sure to validate your inputs or only add points from trusted sources. (eips.ethereum.org)
  • Mapping precompiles:

    • The precompiles at 0x10/0x11 map field elements (Fp / Fp2) into curve points. Just a quick note: they don’t hash bytes into field elements, so you'll need to run hash_to_field on your own (check out IETF RFC 9380) before calling MAP. (eips.ethereum.org)

Just to give you a bit of background, EIP-4844 has this KZG point-evaluation precompile hanging out at 0x0A, and it rolls with a fixed 192-byte ABI. You'll usually want to give it a call before doing your BLS verification, especially when the message you're checking is stored in a blob. You can find more details over at eips.ethereum.org.


BLS on Ethereum: what the beacon chain taught us

  • Ethereum's consensus is built on BLS12-381, using public keys in G1 (48 bytes when compressed) and signatures in G2 (compressed down to 96 bytes). This setup aligns nicely with the IETF BLS ciphersuites and the Eth2 specifications. For on-chain verification, uncompressed points are utilized in line with EIP-2537. You can dive deeper into the details here.
  • Here are some aggregation patterns to keep in mind:
    • Basic verify: This is where you check if e(pk, H(m)) equals e(G1, σ).
    • Fast aggregate verify: If you've got the same message but a bunch of signers, you just sum up the public keys: aggregate pk = ∑ pk_i (in G1). Then you verify with e(agg_pk, H(m)) equals e(G1, σ).
    • Aggregate verify: For distinct messages, you’d check if e(pk1, H(m1)) multiplied by … up to e(pkn, H(mn)) equals e(G1, σ). You can handle these as one pairing_check on k+1 pairs by negating one operand, making sure the product equals 1 in the target field. If you want to explore this further, check out the notes here.

Byte layouts you must get right

  • Make sure to use big-endian for EIP-2537 inputs across the board. While Solidity typically uses little-endian for its ABI types when dealing with integers in memory, it's best not to reinterpret cast. Instead, build the exact byte sequence using abi.encodePacked. You can find more details here.
  • When it comes to supplying points, go for the uncompressed version directly. Decompressing on-chain costs more gas than just sending the decompressed coordinates in your calldata. The spec points this out, and it can save you some real money when scaled. Check it out here.
  • For the infinity point, remember that it's encoded as all-zero bytes for the point size. This can help you set guard rails in your decoders. More info can be found here.

Minimal Solidity: calling the pairing precompile

Goal: Verifying a Single Signature

We want to check a single signature using the “pubkey in G1, signature in G2” approach, specifically against hash_to_G2(m).

Steps to Verify the Signature:

  1. Get the public key and signature: Ensure you have the public key in G1 and the signature in G2 ready.
  2. Hash the message: Use the hash_to_G2(m) function to hash your message m to G2.
  3. Perform the verification: You’ll need to verify the signature by checking the appropriate pairing relationships.

Here’s a quick code snippet to illustrate the verification process:

def verify_signature(pub_key, signature, message):
    hashed_message = hash_to_G2(message)
    
    # Example pairing check (replace with actual pairing function)
    return pairing(pub_key, signature) == pairing(g, hashed_message)
  1. Check the result: If the verification returns true, the signature is valid!

Note:

Be mindful that both the message and the signature need to be formatted correctly, or else the verification won’t work.

Happy coding!

  • Equation: e(pk, H) == e(G1, σ)
  • To pass the pairing check, we need to make sure the product equals 1. So, we use the pairs (pk, H) and (−G1, σ).

In this snippet, we steer clear of any complicated big-number calculations by asking the caller to either precompute −G1 just once (which stays constant) or to give us a negated σ. When it comes to production, our best practice is to precompute −G1 and set it up as an immutable constant.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

// 7Block Labs: minimalistic BLS12-381 pairing check via EIP-2537 (Pectra)
// Inputs must be uncompressed big-endian encodings per EIP-2537.
// - pkG1: 128 bytes (x||y)
// - HmG2: 256 bytes (x_c0||x_c1||y_c0||y_c1) - this is hash_to_G2(m), see notes below
// - sigG2: 256 bytes
// - negG1: 128 bytes encoding of -G1 generator (precomputed once and reused)
library BLS12381Verify {
    address constant PAIRING = address(0x0f); // BLS12_PAIRING_CHECK

    function verifyBasic(
        bytes memory pkG1,
        bytes memory HmG2,
        bytes memory sigG2,
        bytes memory negG1
    ) internal view returns (bool ok) {
        require(pkG1.length == 128 && HmG2.length == 256 && sigG2.length == 256 && negG1.length == 128, "bad-len");

        // Build input to pairing precompile: concat of k slices, each 128 (G1) + 256 (G2) = 384 bytes.
        bytes memory input = new bytes(384 * 2);
        // slice 1: (pkG1, HmG2)
        assembly {
            let ptr := add(input, 32)
            // copy pkG1
            calldatacopy(ptr, add(pkG1, 32), 128)
            // copy HmG2
            calldatacopy(add(ptr, 128), add(HmG2, 32), 256)
            // slice 2: (-G1, sigG2)
            calldatacopy(add(ptr, 384), add(negG1, 32), 128)
            calldatacopy(add(ptr, 512), add(sigG2, 32), 256)
        }

        // Output is 32 bytes: last byte 0x01 if true, else 0x00.
        bytes memory out = new bytes(32);
        assembly {
            if iszero(staticcall(gas(), PAIRING, add(input, 32), mload(input), add(out, 32), 32)) {
                revert(0, 0)
            }
            ok := eq(byte(31, mload(add(out, 32))), 1)
        }
    }
}

Notes:

  • negG1 is the G1 generator where the y-coordinate is negated modulo p. You only need to compute this once off-chain using a solid BLS12-381 library and then hard-code it; it stays constant. Ethereum's fixed generators are standardized, so you can derive −G1 from the published coordinates of the G1 generator. Check it out here: (eth2book.info).
  • If you'd rather negate the signature instead, just pass (pk, H) along with (G1, −σ). Keep in mind that negating in G2 flips the y-coordinate modulo p for both Fp components--do this part off-chain to steer clear of the heavy big-int code that Solidity requires.
  • The pairing precompile already makes sure that the inputs are part of the subgroup, but don’t depend on that when adding points with G1ADD/G2ADD since those skip subgroup checks. You can get more details here: (eips.ethereum.org).

Getting H = hash_to_G2(m) right

EIP-2537 gives you access to map_fp2_to_g2 (0x11), but when it comes to translating “hash bytes → field elements” (like expand_message_xmd), that’s up to you. The IETF’s RFC 9380 lays out the guidelines for hashing to BLS12-381 G2 (for instance, BLS12381G2_XMD:SHA-256_SSWU_RO_), including cofactor clearing. Usually, in production setups, this process happens off-chain, and the resulting uncompressed G2 point gets sent to the contract. If you're looking to handle everything on-chain, you'll need to connect the SHA-256 precompile, implement expand_message_xmd, and then call MAP_FP2_TO_G2 for that mapping step. Check it out here: (ietf.org).

Practical Advice:

  • If you're working with wallets or bridges that you have full control over, make sure to specify the exact ciphersuite in your specifications and reject anything outside of that.
  • For open systems, ask the submitter to provide H as a point along with a “hash-to-curve proof” (SNARK) if you can't fully trust their hashing process. This way, you only need to verify the proof and the pairing, which helps keep gas costs down and minimizes trust.

Fast-aggregate verify with on-chain aggregation

When multiple signers get together to sign the same message ( m ):

  • First off, compute ( \text{agg_pk} = \sum pk_i ). It’s best to use G1MSM (0x0c) with all scalars set to 1 for optimal gas efficiency. This method is quicker than doing repeated ECADD since it utilizes Pippenger’s algorithm, which helps spread out the overhead of calls. After that, just verify that ( e(\text{agg_pk}, H(m)) == e(G1, \sigma) ). You can find more details at (eips.ethereum.org).

Sketch:

Here's a simple guide to getting started with Sketch, a popular design tool used by many UI/UX designers.

Getting Started with Sketch

  1. Download and Install Sketch

    • Head over to the Sketch website to grab the latest version. Just follow the prompts to install it on your Mac.
  2. Familiarize Yourself with the Interface

    • Once you open Sketch, take a moment to explore the layout. You'll find the toolbar at the top, the artboard on the center, and the inspector panel on the right.
  3. Create a New Document

    • Click on "File" > "New" to start a fresh document. You can choose a blank canvas or use one of the templates offered.

Basic Tools You’ll Use

  • Shape Tool: Use this to create rectangles, circles, and polygons. Just select the shape you'd like from the toolbar and draw it on your canvas.
  • Text Tool: To add text, hit the "T" key or select the text tool from the toolbar. Click anywhere on the artboard to start typing.
  • Pen Tool: For custom shapes, the pen tool is your best friend. Click to create points and draw your designs.

Utilizing Symbols and Components

Symbols in Sketch make it super easy to reuse elements across your designs. To create a symbol, just select the layers you want, right-click, and choose "Create Symbol." You can then use this symbol in different artboards without having to redesign it.

Exporting Your Work

When you’re ready to show off your design, exporting is a breeze! Go to "File" > "Export," select what you want, and choose your file format--PNG, JPG, or SVG are all solid options.

Helpful Resources

Wrap Up

Sketch is a powerful tool that opens up a world of design possibilities. With a bit of practice, you'll be creating stunning interfaces in no time. Happy designing!

// Pseudocode for using G1MSM to aggregate N pubkeys:
// Input to 0x0c is k slices of (G1 point 128 bytes || scalar 32 bytes).
// Here scalar is 1 for each point.
// Output is a single 128-byte G1 point.
function aggregatePks(bytes[] memory pksG1) internal view returns (bytes memory aggPk128) {
    uint256 n = pksG1.length;
    require(n > 0, "no-pks");
    bytes memory input = new bytes(160 * n); // 128 + 32 per entry
    for (uint256 i = 0; i < n; i++) {
        require(pksG1[i].length == 128, "bad-pk");
        // copy pk
        // write scalar=1 as big-endian 32 bytes
        // ... build input ...
    }
    bytes memory out = new bytes(128);
    assembly {
        if iszero(staticcall(gas(), 0x0c, add(input, 32), mload(input), add(out, 32), 128)) { revert(0, 0) }
    }
    aggPk128 = out;
}

Gas Planning

When it comes to aggregation through MSM, it actually scales sublinearly due to the discount function in the gas schedule. If you’re looking at the costs for pairing, you're looking at around 37,700 plus 2 times 32,600 for the final verification (that's for two pairs). So, for k signers, the overall cost will be MSM (which depends on the input size) plus roughly 102,900 in pairing gas.

Make sure to profile your specific k on Holesky or a local fork, since the precompile’s discount is aware of the input length. You can check out more info here.

Safety Tips:

  • Stick to adding or subgroup-checking only validated keys. Just a heads up: MSM does its subgroup checks, but ECADD doesn't. So, it’s best to treat any raw user-supplied points as untrustworthy until you’ve got proof that they’re safe. You can read more about it here.

Aggregate verify for distinct messages

For a set of key-message pairs ((pk_i, m_i)) where (i=1..n) and a single aggregate signature (\sigma), here’s the breakdown:

  • First, you’ll want to create a pairing input that consists of (n+1) slices: ((pk_1, H(m_1)), \ldots, (pk_n, H(m_n)), (-G1, \sigma)).
  • When it comes to gas costs, you're looking at roughly (37700 + 32600 \times (n+1)). For example, if you’ve got ten messages, that totals to about (37700 + 11 \times 32600 \approx 395,300) gas for the pairing, plus whatever you need for the hash_to_curve and calldata. This makes it quite feasible for a lot of bridge and committee setups. You can dive deeper into this at (eips.ethereum.org).

Interop with EIP-4844 (KZG) in real applications

A common pattern in rollups and DA attestations:

  1. First off, we use the 0x0A KZG point-evaluation precompile to check if a blob’s polynomial evaluates to y at z and aligns with a versioned hash (EIP-4844). You can read more about it here.
  2. Next, we need to make sure that the entity vouching for that blob evaluation--whether it’s a sequencer or a committee--has signed it with BLS, thanks to EIP-2537 pairing.

This whole process gives you data integrity that's anchored in L1 (thanks to KZG) and authentic attestations (courtesy of BLS), all wrapped up with native precompiles from start to finish.


Exact encodings you’ll pass around (copy-paste friendly)

  • Fp modulus (p) for BLS12-381:
    p = 0x1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab
    According to EIP-2537, every Fp encoding has to be a 64-byte big-endian format, with the top 16 bytes set to zero. You can find more info here.
  • G1/G2 point encodings: These are uncompressed (x||y), where both x and y are made up of those 64-byte Fp (or Fp2) encodings. Just so you know, the representation of infinity is all zeros. Check it out here.
  • Pairing precompile output: This gives you a 32-byte blob, and the last byte is what indicates a "true" (0x01) or "false" (0x00) value. You can find further details here.

Best emerging practices we see in audits

  • Make sure to stick with a single BLS ciphersuite across your setup, like BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_, along with the right domain separation tags. Rejecting any other options will help you dodge any downgrade confusion. (ietf.org)
  • Keep your points uncompressed in calldata to steer clear of those pricey on-chain decompression costs. Seriously, it can add up! If your front-end or any off-chain agents are getting compressed inputs (you know, those 48/96 byte ones), decompress them off-chain using blst/herumi, and then send the 128/256 bytes to your contracts. (eips.ethereum.org)
  • Be cautious with addition precompiles when you're dealing with untrusted inputs; they can be a bit dicey. It’s better to go for MSM or pairing since they handle subgroup checks for you. (eips.ethereum.org)
  • Don’t forget to precompute and hard-code constants like G1, −G1, and those byte-aligned modulus fragments. This approach cuts down on complexity and makes audits way less of a headache.
  • For better throughput, batch those verifications into a single pairing_check. The gas expense increases linearly with the number of pairs, with just a small constant, so you'll save money compared to making a bunch of little calls. (eips.ethereum.org)
  • If your messages are coming from blobs, always verify KZG (0x0A) before you put any trust in signatures that reference blob content. (eips.ethereum.org)
  • Keep an eye on EVM equivalence for your target chain. A lot of the big rollups are measuring up to the mainnet precompiles, but double-check that 0x0b-0x11 are active on your L2/testnet before you deploy. You can use Pectra activation info from EIP-7600 to automate those environment checks in CI! (eips.ethereum.org)

Production checklist (copy to your runbook)

  • Network readiness

    • Make sure Pectra (EIP-7600) is activated on the target chain (whether that's mainnet, L2, or testnet). Check that addresses from 0x0b to 0x11 respond. (eips.ethereum.org)
    • If you're working with blobs, double-check that the 0x0A point-evaluation precompile is functioning properly. (eips.ethereum.org)
  • Encoding

    • Each Fp/Fp2 element should be 64/128 bytes in big-endian format, with the top 16 bytes set to zero for every Fp element.
    • All G1/G2 points need to be in uncompressed encoding (128/256 bytes).
    • Represent infinity as all zeros for your point size. (eips.ethereum.org)
  • Security

    • Make sure subgroup checks are enforced by using MSM/pairing for any untrusted points.
    • Before using ECADD, validate the points first to keep everything secure.
    • Follow RFC 9380 for hash-to-curve; ensure you have a pinned domain separation string, and perform the mapping step using 0x10/0x11 or handle it completely off-chain. (ietf.org)
  • Gas & UX

    • Combine batch verifications into single pairing_check calls to help mitigate that hefty 37.7k base cost. (eips.ethereum.org)
    • Compare MSM to repeated ECADD based on your key counts; it's usually better to go with MSM when aggregating multiple keys. (eips.ethereum.org)

Example: contract-level API for BLS aggregator

A Realistic Surface for an ERC-4337-Style Aggregator or Bridge Verifier:

When we talk about creating a solid foundation for something like an ERC-4337-style aggregator or bridge verifier, there are a few key elements to keep in mind.

  1. Understanding ERC-4337:
    First off, ERC-4337 introduces a way to manage user operations off-chain, which can help scale Ethereum. This means fewer transactions on-chain, leading to lower fees and faster confirmations.
  2. User Operation Pool:
    It's essential to have a reliable user operation pool. This is where all the user operations hang out before they hit the blockchain. A well-structured pool will ensure efficiency and cut down on costs.
  3. Verifying Operations:
    When it comes to verifying operations, you want to make sure the process is smooth and secure. This means implementing checks and balances in your design, possibly using zero-knowledge proofs to maintain privacy while verifying transactions.
  4. Smart Contract Interface:
    Your smart contract should have a clear interface, making it easy for developers to interact with it. This interface should clearly define how to send operations and receive feedback about their execution status.
  5. Security Measures:
    Always prioritize security! Incorporate mechanisms such as rate limiting, transaction limits, and fail-safes to protect against any potential abuse or attacks.
  6. User Experience:
    Don't forget about the user experience. Your design should be intuitive and user-friendly. A complicated process can scare users off, so aim for simplicity in how they interact with the system.
  7. Testing and Feedback:
    Last but not least, keep the testing phase thorough. Gather feedback from users and developers alike to refine and improve your aggregator or bridge verifier.

By focusing on these aspects, you can create a solid and efficient ERC-4337-style aggregator or bridge verifier that works well in the crypto ecosystem.

interface IBLSVerifier {
    // Returns true if e( sum(pk_i), H(m) ) == e(G1, sigma )
    // Requires caller to provide:
    // - pkList: array of uncompressed G1 pubkeys (each 128 bytes)
    // - HmG2: uncompressed hash_to_G2(m) (256 bytes)
    // - sigmaG2: uncompressed aggregate signature (256 bytes)
    // - negG1: cached 128-byte encoding of -G1
    function fastAggregateVerify(
        bytes[] calldata pkList,
        bytes calldata HmG2,
        bytes calldata sigmaG2,
        bytes calldata negG1
    ) external view returns (bool);
}

To implement fastAggregateVerify, you can follow these steps:

  1. Calculate the aggregated public key using the multi-scalar multiplication function:

    G1MSM( pk_i, 1 ) → agg_pk
  2. Perform pairings on the following pairs:

    • The first pairing will be between the aggregated public key and the value HmG2:
      Pairing( agg_pk, HmG2 )
    • The second pairing will involve the negation of a value in G1 and sigmaG2:
      Pairing( negG1, sigmaG2 )

By following these steps, you'll be able to implement the fastAggregateVerify function effectively!

This approach reduces the amount of calldata and calls needed, all while keeping things verifiable within a single transaction.


FAQs you’ll get from your team

  • Does Ethereum expose compressed-point decoding in precompiles?
    Nope! EIP-2537 uses uncompressed 128/256-byte encodings for G1/G2, so just send them that way. Check it out here: (eips.ethereum.org)
  • Do I need to worry about endianness?
    Absolutely! EIP-2537 requires big-endian field elements. Make sure to build your calldata with abi.encodePacked, rather than just casting uints. More info is here: (eips.ethereum.org)
  • Can I rely on pairing for subgroup checks?
    Yep! Pairing and MSM have got your back on this one, but ECADD doesn’t. So, plan your designs accordingly! You can read more here: (eips.ethereum.org)
  • What about KZG and blobs?
    Start off with 0x0A (EIP-4844) to validate those blob claims, and then you can verify BLS signatures on them. Here’s the link for more details: (eips.ethereum.org)

Final thought

Before Pectra, BLS in Solidity was just a research project. But after May 7, 2025, it's going to be a feature you can actually use on the Ethereum mainnet. If you take the time to standardize encodings, centralize constants like −G1, and make sure all untrusted inputs go through MSM/pairing precompiles, you’ll enjoy predictable gas costs, cleaner audits, and a smoother journey from your design document to actual production. Check out the details here: (eips.ethereum.org)


References (specs we linked inline):

  • EIP-7600: This one's all about the Pectra meta EIP, covering activation epochs and including other relevant EIPs. You can check it out here.
  • EIP-2537: Dive into the details of BLS12-381 precompiles, including addresses, encodings, and gas formulas. More info can be found here.
  • EIP-4844: This EIP introduces the KZG point-evaluation precompile located at 0x0A. If you're curious, take a look here.
  • RFC 9380: Here’s where you can learn about hashing to elliptic curves. It’s important to choose a BLS ciphersuite and stick with it. Find all the details here.
  • Eth research/docs: If you're interested in G1/G2 generator choices, key/signature sizes, and how aggregation semantics work, head over here.

Description

Ethereum’s Pectra upgrade on May 7, 2025, rolled out native BLS12-381 precompiles (EIP-2537). This guide is here to help you verify both single and aggregate BLS signatures in Solidity. We’ll cover everything you need--addresses, encodings, gas calculations, and production-safe coding patterns. Check it out for all the details! (eips.ethereum.org)

Like what you're reading? Let's build together.

Get a free 30-minute consultation with our engineering team.

Related Posts

7BlockLabs

Full-stack blockchain product studio: DeFi, dApps, audits, integrations.

7Block Labs is a trading name of JAYANTH TECHNOLOGIES LIMITED.

Registered in England and Wales (Company No. 16589283).

Registered Office address: Office 13536, 182-184 High Street North, East Ham, London, E6 2JA.

© 2026 7BlockLabs. All rights reserved.