Shardeum Automates EIP-2930

·

12 min read

Multicall Contract

What is EIP-2930?

EIP-2930, standing tall in the Ethereum Improvement Proposals arena, introduces a revolutionary shift in how transactions are handled. At its core, this enhancement allows users to wield unprecedented control by defining specific addresses and storage slots within a transaction.

EIP-2930: Optional access lists
Adds a transaction type which contains an access list, a list of addresses and storage keys that the transaction plans…
eips.ethereum.org

Why is EIP-2930 Important?

Good news for Shardeum users! With Sphinx 1.X, things just got a whole lot simpler. Now, you don’t have to worry about manually specifying accessLists for Shardeum RPC nodes to navigate shards. Sphinx 1.X takes care of it for you with automated accessList generation. It’s like having a smart guide that knows the way — just sit back, relax, and let Sphinx 1.X handle the details. Enjoy the smoother ride through the Shardeum network without the hassle of dealing with accessList intricacies!

This document is useful for:

-educational purposes
-situations where the automated accessList fails and you need to specific the accessList directly

Where is EIP-2930 Data Located for a Transaction?

Curious about where EIP-2930 data hangs out during a transaction? Look no further than the accessList transaction parameter. This is the designated space where all the nitty-gritty details of EIP-2930, including address and storage slot data, find their cozy spot. It’s like the secret compartment where the transaction keeps everything it needs for smooth sailing through the EIP-2930 waters. So, when you’re on the lookout for where the EIP-2930 action is happening, just peek into the accessList transaction parameter, and you’ll find all the crucial details neatly tucked away.

How you can Define an AccessList for an EIP-2930 Transaction?​

Based on the EIP-2930 specification, the general syntax should be:

-the address [20 bytes]

-then the storage slots being accessed at that address [32 bytes]

Example:

  [
        [
            "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
            [
                "0x0000000000000000000000000000000000000000000000000000000000000003",
                "0x0000000000000000000000000000000000000000000000000000000000000007"
            ]
        ],
        [
            "0xbb9bc244d798123fde783fcc1c72d3bb8c189413",
            []
        ]
    ]

EIP-2930 Optional:​

Transfer SHM on Shardeum Between Wallets:

Send an EIP-2930 transaction with an access list address that has no storage:

  • Javascript
const Web3 = require('web3')
const ethers = require("ethers")
const rpcURL = "https://dapps.shardeum.org"
const web3 = new Web3(rpcURL)
const provider = new ethers.providers.JsonRpcProvider(rpcURL)
const signer = new ethers.Wallet(Buffer.from(process.env.devTestnetPrivateKey, 'hex'), provider);
console.log("User wallet address: " + signer.address)
const transferToWallet = new ethers.Wallet(Buffer.from(process.env.devTestnetPrivateKeyTwo, 'hex'), provider);
console.log("transferToWallet address: " + transferToWallet.address)
createAndSendTx();
async function createAndSendTx() {
    const chainIdConnected = await web3.eth.getChainId();
    console.log("chainIdConnected: "+ chainIdConnected)
    const oneEtherInWeiSHM = "1000000000000000000"
    console.log("oneEtherInWeiSHM: " + oneEtherInWeiSHM)
    const userBalance = await provider.getBalance(signer.address);
    console.log("User Balance [Shardeum SHM]" )
    console.log(ethers.utils.formatEther(userBalance))
    const receiverBalance = await provider.getBalance(transferToWallet.address);
    console.log("Receiver Balance [Shardeum SHM]" )
    console.log(ethers.utils.formatEther(receiverBalance))
    const txCount = await provider.getTransactionCount(signer.address);
    const tx = signer.sendTransaction({
          chainId: chainIdConnected,
          to: transferToWallet.address,
          nonce:    web3.utils.toHex(txCount),
          gasLimit: web3.utils.toHex(300000), // Raise the gas limit to a much higher amount
          gasPrice: web3.utils.toHex(web3.utils.toWei('30', 'gwei')),
          value: oneEtherInWeiSHM,
          type: 1,
          accessList: [
            {
              address: transferToWallet.address,
              storageKeys: []
            }
          ]
    });
    console.log("WAIT FOR TX RECEIPT: ")
    await tx
    console.log("TX RECEIPT: ")
    console.log(tx)
}
  • Python
from web3 import Web3
import json
import os
import time
ShardeumConnectionHTTPS = "https://dapps.shardeum.org/";
web3 = Web3(Web3.HTTPProvider(ShardeumConnectionHTTPS))
chainIdConnected = web3.eth.chain_id
print("chainIdConnected: " + str(chainIdConnected))
devTestnetPrivateKey = str(os.environ['devTestnetPrivateKey']);
userWallet = (web3.eth.account.from_key(devTestnetPrivateKey)).address
print("User Wallet Address: " + userWallet)
devTestnetPrivateKeyTwo = str(os.environ['devTestnetPrivateKeyTwo']);
transferToWallet = (web3.eth.account.from_key(devTestnetPrivateKeyTwo)).address
print("transferToWallet address: " + transferToWallet)
oneEtherInWeiSHM = "1000000000000000000"
print("weiMsgValueToSend: " + oneEtherInWeiSHM)
userBalance =  web3.eth.getBalance(userWallet);
print("User Balance [Shardeum SHM]" )
print(web3.fromWei(userBalance, "ether"))
receiverBalance =  web3.eth.getBalance(transferToWallet);
print("Receiver Balance [Shardeum SHM]" )
print(web3.fromWei(receiverBalance, "ether"))
transferTx = {
    'chainId' : chainIdConnected,
    'nonce':  web3.eth.getTransactionCount(userWallet)       ,
    'to': transferToWallet, #WORKS WITH REGULAR WALLETS BUT CANNOT SEND TO CONTRACT FOR SOME REASON?
    'gas': 2100000, #WORKS WITH 1000000. If not try : Remix > deploy and run transactions
    'gasPrice': web3.toWei('30', 'gwei'), # https://etherscan.io/gastracker
    'value': int(oneEtherInWeiSHM),
    'accessList' :
                [
                    {
                        "address" : transferToWallet,
                        "storageKeys": []
                    }
                ]
}
signed_tx = web3.eth.account.signTransaction(transferTx, devTestnetPrivateKey)
tx_hash = web3.toHex(web3.eth.sendRawTransaction(signed_tx.rawTransaction))
print("TX HASH: " + tx_hash)
time.sleep(15)
receipt = web3.eth.getTransactionReceipt(tx_hash)
print("TX RECEIPT: " + str(receipt) )

Contracts contractToCall and Multicall:

  • Solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
contract contractToCall {
    uint public slot0; //uint is 32 bytes and fills a 32 byte slot. //Do not set 0 manually it wastes gas!
    function set(uint x) public {
        slot0 = x;
    }
}
contract Multicall {
    contractToCall public callContractToCall;
    constructor(address setCallOne) {
        callContractToCall = contractToCall(setCallOne);
    }
    function multiCallRead() public view returns(uint) {
        return callContractToCall.slot0();
    }
    function multiCallWrite(uint x) public {
        callContractToCall.set(x);
    }
}

contractToCall (Single Address)

Send an EIP-2930 transaction with a accessList. The accessList contains the contract’s address and accessed storage slot (or slots). In this case, it will be storage slot0, because it is a single uint storage variable (uint = 256 bits = 32 bytes) which is modified when we call “set(unit)”.

  • Javascript
const Web3 = require('web3')
const ethers = require("ethers")
const rpcURL = "https://dapps.shardeum.org/"
const web3 = new Web3(rpcURL)
const provider = new ethers.providers.JsonRpcProvider(rpcURL)
const signer = new ethers.Wallet(Buffer.from(process.env.devTestnetPrivateKey, 'hex'), provider);
console.log("User wallet address: " + signer.address)
const simpleStorageAddress = '0xE8eb488bEe284ed5b9657D5fc928f90F40BC2d57'
const simpleStorageABI = [{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"set","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"slot0","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
const simpleStorageDeployed = new web3.eth.Contract(simpleStorageABI, simpleStorageAddress)
createAndSendTx();
async function createAndSendTx() {
    const chainIdConnected = await web3.eth.getChainId();
    console.log("chainIdConnected: "+ chainIdConnected)
    const slot0 = await simpleStorageDeployed.methods.slot0().call()
    console.log("slot0: "+ slot0)
    const unixTime = Date.now();
    console.log("UNIX TIME: " + unixTime)
    const txCount = await provider.getTransactionCount(signer.address);
    const tx = signer.sendTransaction({
          chainId: chainIdConnected,
          to: simpleStorageAddress,
          nonce:    web3.utils.toHex(txCount),
          gasLimit: web3.utils.toHex(300000), // Raise the gas limit to a much higher amount
          gasPrice: web3.utils.toHex(web3.utils.toWei('30', 'gwei')),
          data: simpleStorageDeployed.methods.set(unixTime).encodeABI(),
          type: 1,
          accessList: [
            {
              address: simpleStorageAddress,
              storageKeys: [
                "0x0000000000000000000000000000000000000000000000000000000000000000",
              ]
            }
          ]
    });
    console.log("WAIT FOR TX RECEIPT: ")
    await tx
    console.log("TX RECEIPT: ")
    console.log(tx)
}
  • Python
from web3 import Web3
import json
import os
import math
import time
ShardeumConnectionHTTPS = "https://dapps.shardeum.org/";
web3 = Web3(Web3.HTTPProvider(ShardeumConnectionHTTPS))
chainIdConnected = web3.eth.chain_id
print("chainIdConnected: " + str(chainIdConnected))
devTestnetPrivateKey = str(os.environ['devTestnetPrivateKey']);
userWallet = (web3.eth.account.from_key(devTestnetPrivateKey)).address
print("User Wallet Address: " + userWallet)
Contract_At_Address= web3.toChecksumAddress("0xE8eb488bEe284ed5b9657D5fc928f90F40BC2d57");
abi_Contract = json.loads('[{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"set","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"slot0","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]')
contract_Call = web3.eth.contract(address = Contract_At_Address , abi = abi_Contract);
print(contract_Call.functions.slot0().call());
unixTime = int(math.floor( time.time()*(10**3)) )
print("UNIX TIME: " + str(unixTime) )
EIP_2930_tx = {
    'chainId' : chainIdConnected,
    'nonce':  web3.eth.getTransactionCount(userWallet)       ,
    'to': Contract_At_Address, #WORKS WITH REGULAR WALLETS BUT CANNOT SEND TO CONTRACT FOR SOME REASON?
    'gas': 2100000, #WORKS WITH 1000000. If not try : Remix > deploy and run transactions
    'gasPrice': web3.toWei('30', 'gwei'), # https://etherscan.io/gastracker
    'data' : contract_Call.encodeABI(fn_name='set', args=[unixTime]) ,
    'accessList' :
                [
                    {
                        "address" : Contract_At_Address,
                        "storageKeys": [
                            "0x0000000000000000000000000000000000000000000000000000000000000000",
                        ]
                    }
                ]
}
signed_tx = web3.eth.account.signTransaction(EIP_2930_tx, devTestnetPrivateKey)
tx_hash = web3.toHex(web3.eth.sendRawTransaction(signed_tx.rawTransaction))
print("TX HASH: " + tx_hash)
time.sleep(15)
receipt = web3.eth.getTransactionReceipt(tx_hash)
print("TX RECEIPT: " + str(receipt) )

Multicall Storage Read:

Reading contract states cross shard does not need an accessList.

For example, ERC-20 multicall:

  • Solidity
tokenObject.totalSupply()

will work with no accessList cross shard.

In simple terms, Contract Multicall allows changes in the state of other contracts, such as contractToCall. For sharded Shardeum networks like Liberty 2.X and Sphinx, when using Contract Multicall, it’s essential to specify the addresses and storage slots involved in the transaction outside the usual “from” and “to” parameters. This ensures clarity in the interaction between contracts, making the process more straightforward and tailored to the unique structure of sharded networks.

Sphinx Dapp Address codeHash in Storage Slots:

Sphinx (betanet) simplifies things by not requiring the codeHash in storage slots for each externally called address. In Solidity, a programming language for smart contracts, you can obtain the codeHash of a deployed contract on the matching network by checking if an address is a contract. The ethers library in Solidity is a handy tool for fetching this address codeHash.

Essentially, Sphinx (betanet) streamlines the process, making it easier to work with externally called addresses without the need for additional codeHash information in storage slots.

Solidity codeHash Example:

  • Solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
contract addressCodeHash { //From https://soliditydeveloper.com/extcodehash
    function getCodeHash(address account) public view returns (bytes32) {
        bytes32 codeHash;    
        assembly { codeHash := extcodehash(account) }
        return (codeHash);
    }
    function isContractBasedOnHash(address account) public view returns (bool) {
        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
        bytes32 codeHash;    
        assembly { codeHash := extcodehash(account) }
        return (codeHash != accountHash && codeHash != 0x0);
    }
    function isContractBasedOnSize(address addr) public view returns (bool) {
        uint size;
        assembly { size := extcodesize(addr) }
        return size > 0;
    }
}

EIP-2930 accessList Transactions for Multicall Contract to Modify slot0 in contractToCall:

  • Javascript
const Web3 = require('web3')
const ethers = require("ethers")
const rpcURL = "https://dapps.shardeum.org/"
const web3 = new Web3(rpcURL)
const provider = new ethers.providers.JsonRpcProvider(rpcURL)
const signer = new ethers.Wallet(Buffer.from(process.env.devTestnetPrivateKey, 'hex'), provider);
console.log("User wallet address: " + signer.address)
const contractAddress_JS = '0xb1fEf690f84241738b188eF8b88e52B2cc59AbD2'
const contractABI_JS = [{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"multiCallWrite","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"setCallOne","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"callContractToCall","outputs":[{"internalType":"contractcontractToCall","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"multiCallRead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
const contractDefined_JS = new web3.eth.Contract(contractABI_JS, contractAddress_JS)
createAndSendTx();
async function createAndSendTx() {
    const chainIdConnected = await web3.eth.getChainId();
    console.log("chainIdConnected: "+ chainIdConnected)
    const slot0 = await contractDefined_JS.methods.multiCallRead().call()
    console.log("slot0: "+ slot0)
    const contractOneAddress = await contractDefined_JS.methods.callContractToCall().call()
    console.log("contractOneAddress: "+ contractOneAddress)
    const codeHash = await provider.getCode(contractOneAddress)
    console.log("contractOneAddress codeHash: " + codeHash)
    const unixTime = Date.now();
    console.log("UNIX TIME: " + unixTime)
    const txCount = await provider.getTransactionCount(signer.address);
    const tx = signer.sendTransaction({
        chainId: chainIdConnected,
        to: contractAddress_JS,
        nonce:    web3.utils.toHex(txCount),
        gasLimit: web3.utils.toHex(2100000), // Raise the gas limit to a much higher amount
        gasPrice: web3.utils.toHex(web3.utils.toWei('30', 'gwei')),
        data: contractDefined_JS.methods.multiCallWrite(unixTime).encodeABI(),
        type: 1,
        accessList: [
          {
            address: contractOneAddress, //Contract address we are calling from the "to" contract at some point.
            storageKeys: [
              "0x0000000000000000000000000000000000000000000000000000000000000000",
              codeHash, //Code hash from EXTCODEHASH https://blog.finxter.com/how-to-find-out-if-an-ethereum-address-is-a-contract/
            ]
          }
        ]
    });
    console.log("WAIT FOR TX RECEIPT: ")
    await tx
    console.log("TX RECEIPT: ")
    console.log(tx)
}
  • Python
from web3 import Web3
import json
import os
import time
import math
ShardeumConnectionHTTPS = "https://dapps.shardeum.org/";
web3 = Web3(Web3.HTTPProvider(ShardeumConnectionHTTPS))
chainIdConnected = web3.eth.chain_id
print("chainIdConnected: " + str(chainIdConnected))
devTestnetPrivateKey = str(os.environ['devTestnetPrivateKey']);
userWallet = (web3.eth.account.from_key(devTestnetPrivateKey)).address
print("User Wallet Address: " + userWallet)
multicallContractAddress= web3.toChecksumAddress("0xb1fEf690f84241738b188eF8b88e52B2cc59AbD2");
multicallContractABI = json.loads('[{"inputs":[{"internalType":"uint256","name":"x","type":"uint256"}],"name":"multiCallWrite","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"setCallOne","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"callContractToCall","outputs":[{"internalType":"contractcontractToCall","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"multiCallRead","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]')
multicallContractDeployed = web3.eth.contract(address = multicallContractAddress , abi = multicallContractABI);
contractOneAddress = multicallContractDeployed.functions.callContractToCall().call()
print("contractOneAddress: "+contractOneAddress)
slot0 = multicallContractDeployed.functions.multiCallRead().call()
print("slot0: "+ str(slot0) )
codeHashBytes32 =  (web3.eth.get_code(contractOneAddress))
codeHashString = codeHashBytes32.hex()
print("contractOneAddress codeHash: " + codeHashString )
unixTime = int(math.floor( time.time()*(10**3)) )
print("UNIX TIME: " + str(unixTime) )
EIP_2930_tx = {
  'chainId' : chainIdConnected,
  'to': multicallContractAddress, #WORKS WITH REGULAR WALLETS BUT CANNOT SEND TO CONTRACT FOR SOME REASON?
  'nonce':  web3.eth.getTransactionCount(userWallet)       ,
  'gas': 2100000, #WORKS WITH 1000000. If not try : Remix > deploy and run transactions
  'gasPrice': web3.toWei('30', 'gwei'), # https://etherscan.io/gastracker
  'data' : multicallContractDeployed.encodeABI(fn_name='multiCallWrite', args=[unixTime]) ,
  'type' : 1,
  'accessList' :
              [
                  {
                      "address" : contractOneAddress,
                      "storageKeys": [
                          "0x0000000000000000000000000000000000000000000000000000000000000000",
                          codeHashString  ##Code hash from EXTCODEHASH https://blog.finxter.com/how-to-find-out-if-an-ethereum-address-is-a-contract/
                      ]
                  }
              ]
}
signed_tx = web3.eth.account.signTransaction(EIP_2930_tx, devTestnetPrivateKey)
tx_hash = web3.toHex(web3.eth.sendRawTransaction(signed_tx.rawTransaction))
print("TX HASH: " + tx_hash)
time.sleep(15)
receipt = web3.eth.getTransactionReceipt(tx_hash)
print("TX RECEIPT: " + str(receipt) )

Simplifying EIP-2930 AccessList Creation

Creating an EIP-2930 accessList just got a whole lot easier! Introducing the EIP-2930 accessList Simulator, a powerful tool designed to generate accessLists with an impressive 91% accuracy.

You can find this user-friendly tool at https://github.com/alexchenzl/predict-al

The process becomes even more seamless with the dedicated GitHub repository at -> https://github.com/shardeumglobalswap/interface/tree/support-eip2930, where you’ll discover EIP-2930 accessList generation specifically tailored for swap transactions.

Dive into the world of streamlined accessList creation and make the most out of these resources for a hassle-free experience with EIP-2930.

Simplyfying Solidity Interfaces:

Ever wondered how smart contracts communicate seamlessly? Enter Solidity Interfaces, the glue that enables smart contracts to interact harmoniously. These interfaces serve as a communication protocol, allowing different smart contracts to understand and work with each other. In essence, Solidity interfaces streamline the way these digital entities communicate and collaborate within the blockchain ecosystem. Stay tuned as we unravel the intricacies of Solidity interfaces and explore how they play a pivotal role in enhancing the interoperability of smart contracts.

The Power of Solidity Interfaces: Versatility Unleashed

Solidity interfaces emerge as a dynamic tool within the blockchain realm for two compelling reasons.
Firstly, they facilitate seamless communication between contracts developed with different Solidity versions, breaking down barriers and fostering compatibility. This means contracts can interact fluidly, regardless of the version disparities.

Secondly, Solidity interfaces empower developers to cherry-pick and utilize only the functions essential to their specific use case. This not only streamlines the code but also enhances efficiency, allowing for a tailored and purpose-driven approach in smart contract development. In essence, Solidity interfaces stand as a testament to adaptability and precision in the ever-evolving landscape of decentralized applications.

Crafting Solidity Interfaces:

Defining a Solidity Interface is a straightforward process that enhances the interaction between smart contracts. To set the stage, place the contract interface above the contract that intends to call it. Within the interface, articulate functions based on their names, inputs, and modifiers. This step involves specifying the blueprint of the functions without implementing their logic. Think of it as a contract handshake, where the interface outlines the terms of communication, and the implementing contract agrees to adhere to these terms. This methodical approach ensures a clear and standardized interaction protocol between contracts, promoting a seamless exchange of information within the blockchain ecosystem.

When should I use a Solidity Interface?

One popular contract that is useful with interfaces is WETH (Wrapped Ether). This contract converts MSG.VALUE into an ERC-20 instance. On Shardeum, SHM Is MSG.VALUE. Therefore, WETH is WSHM on Shardeum. This contract has wrap and unwrap functions:

Deposit (Wrap): SHM => WSHM.

Withdraw (Unwrap): WSHM => SHM.

Using a WSHM interface, we can create a Solidity 0.8.0 contract which can interact with WSHM (Solidity 0.4.0).

WSHM Solidity 0.4.0

  • Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.4.18;
contract WSHM {
    string public name     = "Wrapped SHM";
    string public symbol   = "WSHM";
    uint8  public decimals = 18;
    event  Approval(address indexed src, address indexed guy, uint wad);
    event  Transfer(address indexed src, address indexed dst, uint wad);
    event  Deposit(address indexed dst, uint wad);
    event  Withdrawal(address indexed src, uint wad);
    mapping (address => uint)                       public  balanceOf;
    mapping (address => mapping (address => uint))  public  allowance;
    function() public payable {
        deposit();
    }
    function deposit() public payable {
        balanceOf[msg.sender] += msg.value;
        Deposit(msg.sender, msg.value);
    }
    function withdraw(uint wad) public {
        require(balanceOf[msg.sender] >= wad);
        balanceOf[msg.sender] -= wad;
        msg.sender.transfer(wad);
        Withdrawal(msg.sender, wad);
    }
    function totalSupply() public view returns (uint) {
        return this.balance;
    }
    function approve(address guy, uint wad) public returns (bool) {
        allowance[msg.sender][guy] = wad;
        Approval(msg.sender, guy, wad);
        return true;
    }
    function transfer(address dst, uint wad) public returns (bool) {
        return transferFrom(msg.sender, dst, wad);
    }
    function transferFrom(address src, address dst, uint wad)
        public
        returns (bool)
    {
        require(balanceOf[src] >= wad);
        if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
            require(allowance[src][msg.sender] >= wad);
            allowance[src][msg.sender] -= wad;
        }
        balanceOf[src] -= wad;
        balanceOf[dst] += wad;
        Transfer(src, dst, wad);
        return true;
    }
}

WSHM Interface Solidity 0.8.0 With Multicall Contract

  • Solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
interface interfaceWSHM {
    function transfer(address dst, uint wad) external returns (bool);
    function transferFrom(address src, address dst, uint wad) external returns (bool);
    function withdraw(uint wad) external;
    function deposit() external payable;
}
contract multicallWSHM {
    interfaceWSHM public WSHM;
    receive() external payable {}
    fallback() external payable {}
    constructor() {
        WSHM = interfaceWSHM(0xa80d5d2C8Cc0d06fBC1F1A89A05d76f86082C20e); // WSHM address.
    }
    function multicallDeposit() public payable {
        WSHM.deposit{value: msg.value}();
        WSHM.transfer(msg.sender, msg.value);
    }
    function multicallWithdraw(uint256 amount) public {
        WSHM.transferFrom(msg.sender,address(this),amount);
        WSHM.withdraw(amount);
        payable(msg.sender).transfer(address(this).balance);
    }
}

Conclusion:

In conclusion, Shardeum stands at the forefront of blockchain innovation, exemplified by its seamless integration of EIP-2930 and the groundbreaking capabilities of Sphinx 1.X. The commitment to user-centric advancements is evident, with Sphinx 1.X automating accessList for Shardeum RPC nodes, simplifying shard routing, and enhancing the overall user experience. The strategic utilization of EIP-2930 showcases Shardeum’s dedication to staying ahead of the curve, providing users with efficient tools for managing address and storage slot data. The simplicity of Solidity interfaces further emphasizes Shardeum’s commitment to versatility and interoperability within the decentralized landscape. As we witness these technological marvels unfold, it becomes clear that Shardeum is not just embracing the future of blockchain but actively shaping it, making it a promising platform for users and developers alike.

Follow Shardeum on ->

Official Website -> https://shardeum.org/
Twitter -> https://twitter.com/shardeum
Yoututbe -> https://youtube.com/shardeum
Discord -> https://discord.gg/shardeum