npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@frugalwizard/offchain-aggregate

v1.0.0

Published

A way to read aggregate state from an EVM blockchain without having to deploy a contract for that specific purpose

Downloads

1

Readme

README

What is this?

This is a solidity contract which can be used to read aggregate data (constructed using any arbitrary code) from a blockchain without having to actually deploy the contract.

For example, let's say you have an NFT contract that implements the Enumerable interface and you want to know all the NFTs owned by an address, you could iterate over each index and call tokenOfOwnerByIndex() for each index, which means you would make a request to your endpoint for each token.

This could be achieved with one request if there was a function that iterated over all index and compiled an array with all the tokens, but this means that function needs to be deployed somewhere for it to be called... except it does not need to.

Using the Aggregator contract you can write any view function you want and run it in a static call to obtain whatever data you want. Just keep in mind... it's a bit of hack (as in using stuff not the way it was intended to).

How does this work?

The trick here is that when you deploy a contract you are actually running the code for the constructor, and you can static call a deploy transaction so it is technically possible to run arbitrary code in a static call.

The deploy code (constructor) returns the bytecode of the contract, which is what is returned if you static call a deploy transaction. But we can use assembly to hijack this and return whatever we want.

How do I use this?

Best way to understand this is with an example, let's make the NFT aggregator we talked about before:

contract NFTAggregator is Aggregator {
    constructor(IERC721Enumerable nft, address owner)
        onlyStaticCall
        Aggregator(abi.encode(_aggregateNFTs(nft, owner)))
    {
        // constructor should be empty, anything here won't be executed

        // the onlyStaticCall modifier is optional, it's just a safeguard
        // to prevent the contract from being mistakenly deployed
    }

    // contract should not have public/external functions
    // it would only increase the payload unnecessarily

    function _aggregateNFTs(IERC721Enumerable nft, address owner)
        private view
        returns (uint[] memory)
    {
        uint[] memory tokens = new uint[](nft.balanceOf(owner));
        for (uint i = 0; i < tokens.length; i++) {
            tokens[i] = nft.tokenOfOwnerByIndex(owner, i);
        }
        return tokens;
    }
}

And this is how we use it:

// using Ethers 6.x

async function getNFTsOwnedBy(provider, nftAddress, ownerAddress) {
    const factory = new ethers.ContractFactory(
        NFT_AGGREGATOR_ABI, NFT_AGGREGATOR_BYTECODE
    );
    return ethers.AbiCoder.defaultAbiCoder().decode(
        [ 'uint[]' ],
        await provider.call(
            await factory.getDeployTransaction(nftAddress, ownerAddress)
        )
    )[0];
}
// using web3 4.x

async function getNFTsOwnedBy(web3, nftAddress, ownerAddress) {
    const contract = new web3.eth.Contract(NFT_AGGREGATOR_ABI);
    return web3.eth.abi.decodeParameter(
        'uint[]',
        await web3.eth.call({
            from: '0x0000000000000000000000000000000000000000',
            data: contract.deploy({
                data: NFT_AGGREGATOR_BYTECODE,
                arguments: [nftAddress, ownerAddress],
            }).encodeABI(),
        })
    );
}