Story Story
07 February 2025
© Story Foundation 2025

Learn

WhitepaperBlogFAQs

Build

Getting StartedDocsGitHubBrand Kit

Tools

Block ExplorerProtocol ExplorerFaucetStaking

Explore

EcosystemBridgeIP Portal

Community

CareersGovernanceForum

Legal

PrivacyTerms of UseEnd User Terms
Upgrading to Pectra
back

Upgrading to Pectra

Story

Story

05 June 2025

Tech

Introduction

We're excited to announce Story's upgrade to maintain maximum compatibility with the latest EVM ecosystem. We're upgrading our Geth-based execution environment to incorporate important changes from the recent Ethereum Pectra hard fork. This upgrade reflects our commitment to maintaining an efficient, fully compatible execution layer while providing developers with new capabilities.

The Pectra upgrade introduces significant changes, including enhancements in account abstraction, validator operations, and network performance. This is done through several EIPs that enhance user/developer experience, improve performance, and increase network resiliency. While some EIPs focus on consensus and staking parameters, these are not directly applicable to Story, as our powerful consensus engine already incorporates many of these features.

Given these significant changes to the user experience, we aim to be among the first blockchains to incorporate them while maintaining maximum user safety. Thus, over the past few months, we have planned, reviewed, and prepared a series of phased upgrades with rigorous and comprehensive testing, prioritizing changes that deliver the greatest value to Story users and developers. Here are the key updates included in the first network upgrade:

  • EIP-7702 – Set codes for EOAs

    This EIP introduces a new transaction type that enables lightweight account abstraction, supporting features like batching, spend limits, and off-chain authorization—all without needing a full smart contract wallet.

  • EIP-2537 – Precompile for BLS12-381 curve operations

    This EIP adds native support for BLS12-381 curve operations, enabling efficient BLS signature verification in the EVM — a major win for ZK systems, light clients, and staking.

  • EIP-7623 – Increase calldata cost

    This EIP increases calldata cost per byte (from 4/16 to 10/40 gas), reducing the maximum payload size per block (from ~8.6MB to ~3.4MB). This discourages abuse from high-volume data transactions without affecting standard DeFi, social, or bridging use cases.

These updates are currently deployed on Aeneid Testnet and will soon be on Mainnet. As part of the next update, we are deploying additional EIPs, including EIP-2925, and combining EIP-7702 with our current Web2-friendly registration system built on ERC-4337. This integration will provide a smoother onboarding experience while improving our control over execution, gas abstraction, and security rules.

To better demonstrate how EIP-7702 can dramatically improve user experience, here's a practical demo of its capabilities.

Using EIP-7702 for Account Abstraction: A Minimal Demo

Demo Contract Overview

We use a simple Counter.sol contract that supports signature-based access control. This allows EOAs to temporarily act like a smart contract when used with EIP-7702.

In a real-world use case, such as with smart contract wallets (e.g., MetaMask Snaps or Safe modules), the logic would be much more complex, unlocking rich features like batching, permissions, and meta-transactions. This simple example is meant to demonstrate how EIP-7702 works in principle.

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

contract Counter {
    uint256 public number;
    bool public initialized;

    modifier onlyInitialized() {
        require(initialized, "Not initialized");
        _;
    }

    /// Initializes the contract. Can only be called once.
    /// The signature must be over keccak256(abi.encodePacked("initialize", initialValue))
    function initialize(uint256 initialValue, bytes calldata signature) external {
        require(!initialized, "Already initialized");

        bytes32 hash = keccak256(abi.encodePacked("initialize", initialValue));
        bytes32 ethSignedHash = keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
        );

        (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
        address signer = ecrecover(ethSignedHash, v, r, s);
        require(signer == address(this), "Invalid signature");

        number = initialValue;
        initialized = true;
    }

    /// Sets a new value. Requires a valid signature.
    /// The signature must be over keccak256(abi.encodePacked("setNumber", newValue))
    function setNumber(uint256 newValue, bytes calldata signature) external onlyInitialized {
        bytes32 hash = keccak256(abi.encodePacked("setNumber", newValue));
        bytes32 ethSignedHash = keccak256(
            abi.encodePacked("\x19Ethereum Signed Message:\n32", hash)
        );

        (bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
        address signer = ecrecover(ethSignedHash, v, r, s);
        require(signer == address(this), "Invalid signature");

        number = newValue;
    }

    /// Returns the current value
    function getNumber() external view returns (uint256) {
        return number;
    }

    /// Splits a 65-byte signature into (r, s, v)
    function splitSignature(bytes memory sig)
        internal
        pure
        returns (bytes32 r, bytes32 s, uint8 v)
    {
        require(sig.length == 65, "Invalid signature length");
        assembly {
            r := mload(add(sig, 0x20))
            s := mload(add(sig, 0x40))
            v := byte(0, mload(add(sig, 0x60)))
        }
    }
}

Step-by-Step: Setting Up and Testing

We'll now walk through how to use EIP-7702 to deploy this contract and delegate its logic to an EOA account.

0. Ensure Pectra Is Activated and EOA_1 Has Balance

Sample configuration:

# Story Aeneid Testnet RPC: <https://aeneid.storyrpc.io>
ETH_RPC_URL=http://localhost:8545

PRIVATE_KEY= PRIKEY_EOA_1
ACCOUNT= ADDR_EOA_1

1. Deploy the Contract

Use Foundry to deploy the Counter contract.

You may need to adjust gas-price, priority-gas-price, and gas-limit depending on your chain's state and the contract complexity.

# 1.1 Initialize a new Foundry project
forge init foundry && cd foundry

# 1.2 Replace src/Counter.sol with the above demo contract

# 1.3 Deploy the contract
 forge create --rpc-url $ETH_RPC_URL \
  --private-key $PRIKEY_EOA_1 \
  src/Counter.sol:Counter \
  --broadcast \
  --gas-price 100gwei \
  --priority-gas-price 20gwei \
  --gas-limit 1000000

Sample output:

Deployer: ADDR_EOA_1
Deployed to: ADDR_CONTRACT
Transaction hash: 0x347ba....5ea1

2. Create a New EOA

Generate a new EOA using cast wallet new.

Sample output:

Address:     ADDR_EOA_2
Private key: PRIKEY_EOA_2

The EOA_2 doesn't need to have a positive balance.

3. Sign and Send the EIP-7702 Auth Transaction

Use the new EOA to authorize the deployed contract code as its runtime logic:

cast wallet sign-auth ADDR_CONTRACT \
  --private-key PRIKEY_EOA_2

This returns the AUTH payload. Now send a setCodeTx with EOA_1 to set the delegation:

cast send $(cast az) \
  --private-key $PRIKEY_EOA_1 \
  --auth AUTH

4. Verify Contract Delegation

Confirm the EOA_2 now has deployed contract logic:

# Expected output: 0xef0100+ADDR_CONTRACT
cast code ADDR_EOA_2 \
  --rpc-url $ETH_RPC_URL

# Expected output: 1
cast nonce ADDR_EOA_2 \
  --rpc-url $ETH_RPC_URL

# Expected output: 0x0000000000000000000000000000000000000000000000000000000000000000. It means not initialized.
cast call ADDR_EOA_2 "getNumber()" --rpc-url ETH_RPC_URL

5. Init EOA_2

Under EIP-7702, code is set on an EOA without executing initcode, which means you can't initialize storage during deployment. To avoid front-running attacks — where an observer could initialize the account with malicious values — developers should require the initial setup call to be signed by the original EOA and verified via ecrecover. This ensures only the intended initialization is accepted.

Here’s an example shell script to initialize the contract. Replace the variables as needed:

#!/bin/bash

set -e

INIT_VALUE=1
PRIKEY_EOA_2=$PRIKEY_EOA_2
ADDR_EOA_2=$ADDR_EOA_2
RPC_URL=$RPC_URL
CHAIN_ID=$CHAIN_ID
PRIKEY_EOA_1=$PRIKEY_EOA_1

# construct hash
METHOD_HEX=$(echo -n "initialize" | xxd -p -c256)
VALUE_HEX=$(printf "%064x" $INIT_VALUE)
PACKED_HEX="${METHOD_HEX}${VALUE_HEX}"
HASH=$(cast keccak256 "0x$PACKED_HEX")

# sign hash
SIG=$(cast wallet sign --private-key $PRIKEY_EOA_2 $HASH)
echo "Signature: $SIG"

# call initialize(uint256, bytes)
echo "Calling initialize..."
cast send $ADDR_EOA_2 "initialize(uint256,bytes)" $INIT_VALUE $SIG \
    --rpc-url $RPC_URL \
    --private-key $PRIKEY_EOA_1 \
    --chain $CHAIN_ID \
    --gas-price 100gwei --priority-gas-price 20gwei --gas-limit 1000000

Confirm the EOA_2 has been initialized successfully:

# Expected output: 0x0000000000000000000000000000000000000000000000000000000000000001.
cast call ADDR_EOA_2 "getNumber()" --rpc-url RPC_URL

6. Call EOA_2 Like a Smart Contract

Once initialized, you can interact with EOA_2 like any smart contract. This is similar to the initialization step, but now calling other contract methods.

Here's a shell script to call setNumber():

#!/bin/bash

set -e

NEW_VALUE=2
PRIKEY_EOA_2=$PRIKEY_EOA_2
ADDR_EOA_2=$ADDR_EOA_2
RPC_URL=$RPC_URL
CHAIN_ID=$CHAIN_ID
PRIKEY_EOA_1=$PRIKEY_EOA_1 

METHOD_HEX=$(echo -n "setNumber" | xxd -p -c256)
VALUE_HEX=$(printf "%064x" $NEW_VALUE)
PACKED_HEX="${METHOD_HEX}${VALUE_HEX}"
HASH=$(cast keccak "0x$PACKED_HEX")

SIG=$(cast wallet sign --private-key $PRIKEY_EOA_2 $HASH)

echo "Calling setNumber..."
cast send $ADDR_EOA_2 "setNumber(uint256,bytes)" $NEW_VALUE $SIG \
    --rpc-url $RPC_URL \
    --private-key $PRIKEY_EOA_1 \
    --chain $CHAIN_ID \
    --gas-price 100gwei --priority-gas-price 20gwei --gas-limit 1000000

You might also like

Story Network Postmortem

Story Network Postmortem

Bug Bounty Criticals Seamlessly Patched
Tech
01 Jul 2025
Private Key Encryption for Validators

Private Key Encryption for Validators

New Features in Story CLI
Tech
08 May 2025
Exploring IP Privacy with Fully Homomorphic Encryption

Exploring IP Privacy with Fully Homomorphic Encryption

How confidential IP interactions can work
Tech
01 May 2025

Subscribe to our newsletter

Thanks for subscribing!

Sign Up