-
Notifications
You must be signed in to change notification settings - Fork 827
Add EIP-7702 Examples #4015
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add EIP-7702 Examples #4015
Changes from 8 commits
98b6697
0dea9fb
ec0d929
cfb752b
9a74245
b6c0023
4996851
35a1620
9b3dc63
ff7240e
b4ac203
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -64,5 +64,8 @@ | |
"engines": { | ||
"node": ">=18", | ||
"npm": ">=7" | ||
}, | ||
"dependencies": { | ||
"@ethersproject/abi": "^5.8.0" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,9 +57,9 @@ See PR [#3524](https://github.com/ethereumjs/ethereumjs-monorepo/pull/3524): | |
See PR [#3544](https://github.com/ethereumjs/ethereumjs-monorepo/pull/3544): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Commenting here, since I can't do above: accicental removal (+ committed) of the I think it might generally be wise to just pick the relevant changes from this PR (by just copying over) and submit a new PR, best from a branch on your side ( There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok ill do it |
||
|
||
- `Address.zero()` -> `createZeroAddress()` | ||
- `Address.fromString()` -> `createAddressFromString()` | ||
- `new Address(hexToBytes())` -> `createAddressFromString()` | ||
- `Address.fromPublicKey()` -> `createAddressFromPublicKey()` | ||
- `Address.fromPrivateKey()` -> `createAddressFromPrivateKey()` | ||
- `new Address(privateToAddress())` -> `createAddressFromPrivateKey()` | ||
- `Address.generate()` -> `createContractAddress()` | ||
- `Address.generate2()` -> `createContractAddress2()` | ||
- New: `createAddressFromBigInt()` | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
# EIP-7702 Examples for EthereumJS | ||
|
||
This directory contains examples demonstrating how to use [EIP-7702](https://eip7702.io/) with the EthereumJS libraries. EIP-7702 allows EOAs (Externally Owned Accounts) to set their code based on existing smart contracts, giving them capabilities similar to smart contract accounts without the need to deploy a separate contract. | ||
|
||
## What is EIP-7702? | ||
|
||
EIP-7702 gives EOAs superpowers by allowing them to set their code based on any existing smart contract. An EOA owner signs an authorization that can then be submitted by anyone as part of a new transaction type. This enables EOAs to mimic smart contract accounts, enabling features like: | ||
|
||
- Transaction bundling (execute multiple actions in one transaction) | ||
- Gas sponsorships | ||
- Custom permissioning schemes | ||
|
||
The EOA's code will be valid until replaced by another authorization, and the authorization can be given for a single chain or all chains at once. | ||
|
||
## Benefits of EIP-7702 | ||
|
||
- **Better User Experience**: Users can execute multiple operations atomically in one transaction | ||
- **Gas Efficiency**: Reduce gas costs by combining multiple transactions | ||
- **No Contract Deployment**: Use existing smart contract implementations without deploying your own | ||
- **Flexibility**: Users can provide both single-chain and cross-chain authorizations | ||
- **Compatible with EIP-4337**: Works with account abstraction infrastructure like paymasters and bundlers | ||
- **Compatible with Existing Smart Accounts**: Use existing smart account implementations with little to no effort | ||
|
||
## Examples | ||
|
||
### 1. Enabling EIP-7702 in the VM (`eip7702-enable.ts`) | ||
|
||
This example demonstrates: | ||
- How to enable EIP-7702 in the EthereumJS VM | ||
- Creating and processing an EIP-7702 transaction | ||
- Verifying the EOA's code has been set correctly | ||
|
||
### 2. Atomic ERC20 Operations (`eip7702-erc20-atomic.ts`) | ||
|
||
This example demonstrates: | ||
- Using EIP-7702 for atomic ERC20 token operations (approve + transferFrom) | ||
- Using RPCStateManager to simulate against a real network | ||
- How an EOA can delegate to a bundler contract to perform multiple operations in one transaction | ||
|
||
### 3. Uniswap Swap with EIP-7702 (`eip7702-uniswap.ts`) | ||
|
||
This example demonstrates: | ||
- Using EIP-7702 to perform a Uniswap swap | ||
- Atomically approving tokens and executing a swap in one transaction | ||
- Simulating the transaction using a mainnet fork | ||
|
||
## Running the Examples | ||
|
||
To run these examples, you'll need to: | ||
|
||
1. Make sure you have EthereumJS libraries installed | ||
2. Run a local Ethereum node or use a fork of mainnet for the examples with RPCStateManager | ||
3. Execute the example scripts (e.g., `ts-node eip7702-enable.ts`) | ||
|
||
## Important Notes | ||
|
||
- These examples are for demonstration purposes only | ||
- **NEVER** use real private keys with value in examples or test code | ||
- For RPCStateManager examples, use a local development node or test network | ||
|
||
## Implementation Status | ||
|
||
EIP-7702 is still in the proposal stage. The EthereumJS implementation is intended to help developers understand how to use EIP-7702 and prepare for its potential adoption. | ||
|
||
## Resources | ||
|
||
- [EIP-7702 Website](https://eip7702.io/) | ||
- [EIP-7702 Examples](https://eip7702.io/examples) | ||
- [EthereumJS Documentation](https://github.com/ethereumjs/ethereumjs-monorepo) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
# Blog Post Outline: Implementing EIP-7702 in EthereumJS - The Future of EOA Superpowers | ||
|
||
## Introduction | ||
- Brief explanation of what EIP-7702 is and why it matters | ||
- The problem it solves - UX challenges with EOAs vs. smart contract accounts | ||
- Announcement of EIP-7702 support in EthereumJS libraries | ||
|
||
## Understanding EIP-7702 | ||
- Detailed explanation of EIP-7702 and its mechanism | ||
- How it allows EOAs to delegate to smart contract implementations | ||
- The authorization process and transaction structure | ||
- Security model and considerations | ||
|
||
## Benefits of EIP-7702 | ||
- Transaction bundling for better UX | ||
- Gas efficiency by combining multiple operations | ||
- Removing the need for separate smart contract wallet deployments | ||
- Compatibility with existing tools and infrastructure | ||
- Flexible authorization schemes (single-chain, cross-chain) | ||
|
||
## Implementation in EthereumJS | ||
- Overview of changes made to the EthereumJS libraries | ||
- New transaction type for EIP-7702 | ||
- How the VM processes EIP-7702 transactions | ||
- Verification and code delegation process | ||
|
||
## Example Use Cases | ||
1. **Atomic ERC20 Operations** | ||
- Approving and transferring tokens in a single transaction | ||
- Benefits for DeFi usability | ||
- Code example and explanation | ||
|
||
2. **Uniswap Swap Optimization** | ||
- Token approval and swap in one transaction | ||
- How this improves user experience | ||
- Gas savings and security benefits | ||
- Code example and explanation | ||
|
||
3. **Smart Account Features for EOAs** | ||
- Using ERC-4337 implementations with EOAs | ||
- Compatibility with existing account abstraction infrastructure | ||
- Potential for additional features like social recovery | ||
|
||
## Getting Started with EIP-7702 in EthereumJS | ||
- How to enable EIP-7702 in your EthereumJS implementation | ||
- Building applications that leverage EIP-7702 | ||
- Testing and simulating EIP-7702 transactions | ||
|
||
## Future Outlook | ||
- Potential impact on the Ethereum ecosystem | ||
- How EIP-7702 fits with other account abstraction efforts | ||
- Next steps for EIP-7702 adoption | ||
|
||
## Conclusion | ||
- Summary of EIP-7702's benefits | ||
- Call to action for developers to start experimenting | ||
- Resources for further learning | ||
|
||
## Resources | ||
- Links to the EIP-7702 specification | ||
- EthereumJS documentation and examples | ||
- Community discussion and feedback channels | ||
- GitHub repositories and code examples |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { Chain, Common, Hardfork, Mainnet } from '@ethereumjs/common' | ||
import { Capability, TransactionType, createEOACode7702Tx } from '@ethereumjs/tx' | ||
import { | ||
Address, | ||
bytesToHex, | ||
createAddressFromString, | ||
hexToBytes, | ||
privateToAddress, | ||
} from '@ethereumjs/util' | ||
import { VM } from '@ethereumjs/vm' | ||
|
||
/** | ||
* This example demonstrates how to enable EIP-7702 in the EthereumJS VM | ||
* and how to create and process an EIP-7702 transaction. | ||
*/ | ||
const main = async () => { | ||
// Create a Common instance with EIP-7702 enabled | ||
const common = new Common({ | ||
chain: Mainnet, | ||
hardfork: Hardfork.Cancun, | ||
eips: [7702], | ||
}) | ||
|
||
console.log('Is EIP-7702 activated?', common.isActivatedEIP(7702)) | ||
|
||
// Create accounts for demonstration | ||
const privateKey = hexToBytes( | ||
'0x1122334455667788112233445566778811223344556677881122334455667788', | ||
) | ||
const senderAddress = new Address(privateToAddress(privateKey)) | ||
console.log('Sender address:', senderAddress.toString()) | ||
|
||
// Create VM instance with EIP-7702 enabled | ||
const vm = await (VM as any).create({ common }) | ||
|
||
// Set up account state | ||
const accountBalance = 10n ** 18n // 1 ETH | ||
await vm.stateManager.putAccount(senderAddress, { balance: accountBalance, nonce: 0n }) | ||
|
||
// Smart contract implementation address that we want to delegate to | ||
// This could be any deployed smart contract (e.g. ERC-4337 account implementation) | ||
const implementationAddress = new Address( | ||
hexToBytes('0x0000000000000000000000000000000000000123'), | ||
) | ||
console.log('Implementation address:', implementationAddress.toString()) | ||
|
||
// Create EIP-7702 transaction | ||
// The authorization enables the EOA to use the code from the implementation address | ||
const chainId = common.chainId() | ||
|
||
// Create an authorization for EIP-7702 | ||
// This is normally signed by the EOA owner | ||
const txData = { | ||
nonce: 0n, | ||
gasLimit: 100000n, | ||
maxFeePerGas: 10000000000n, // 10 gwei | ||
maxPriorityFeePerGas: 1000000000n, // 1 gwei | ||
to: senderAddress, // target is the same as sender (self-call for initialization) | ||
value: 0n, | ||
data: hexToBytes('0x'), // Could be initialization data | ||
accessList: [], | ||
// Using just [] as a basic authorizationList that will be accepted | ||
authorizationList: [], | ||
} | ||
|
||
// Create and sign the EIP-7702 transaction | ||
const tx = createEOACode7702Tx(txData, { common }) | ||
const signedTx = tx.sign(privateKey) | ||
|
||
console.log('Transaction type:', TransactionType[signedTx.type]) | ||
console.log('Supports EIP-7702:', signedTx.supports(Capability.EIP7702EOACode)) | ||
console.log('Transaction hash:', bytesToHex(signedTx.hash())) | ||
|
||
// Run the transaction to set the code of the EOA | ||
const result = await vm.runTx({ tx: signedTx }) | ||
|
||
console.log( | ||
'Transaction processed:', | ||
result.execResult.exceptionError !== null && result.execResult.exceptionError !== undefined | ||
? 'Failed' | ||
: 'Success', | ||
) | ||
|
||
// Check that the EOA now has code | ||
const account = await vm.stateManager.getAccount(senderAddress) | ||
const code = await vm.stateManager.getContractCode(senderAddress) | ||
|
||
console.log('Account nonce after transaction:', account?.nonce) | ||
console.log('Account has code:', code.length > 0) | ||
console.log('Code (hex):', bytesToHex(code)) | ||
|
||
// The code should start with the EIP-7702 delegation flag (0xef0100) | ||
// followed by the implementation address | ||
const hasDelegationFlag = code[0] === 0xef && code[1] === 0x01 && code[2] === 0x00 | ||
console.log('Has delegation flag:', hasDelegationFlag) | ||
|
||
// Extract the delegated address from the code | ||
if (hasDelegationFlag && code.length === 23) { | ||
const delegatedAddressBytes = code.slice(3) | ||
const delegatedAddress = createAddressFromString(bytesToHex(delegatedAddressBytes)) | ||
console.log('Delegated to address:', delegatedAddress.toString()) | ||
|
||
// Verify it matches our implementation address | ||
console.log('Matches implementation address:', delegatedAddress.equals(implementationAddress)) | ||
} | ||
} | ||
|
||
// Helper function for bigint to unpadded bytes conversion | ||
function bigIntToUnpadded(value: bigint): Uint8Array { | ||
// Convert bigint to bytes without padding | ||
const hex = value.toString(16) | ||
return hexToBytes(hex.length % 2 === 0 ? `0x${hex}` : `0x0${hex}`) | ||
} | ||
|
||
main().catch(console.error) | ||
jochem-brouwer marked this conversation as resolved.
Show resolved
Hide resolved
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have an Ethers dev dependency in
package.json
root which you should be able to use, no need to add a dependency here + alter thepackage-lock.json
file.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok