-
Notifications
You must be signed in to change notification settings - Fork 827
tx: 7702 examples #4039
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
base: master
Are you sure you want to change the base?
tx: 7702 examples #4039
Changes from 8 commits
a9a709e
8d76c4b
723e789
4742254
47991de
5eae393
880ffed
130350e
cd80ac7
73f8ee4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
import { Common, Hardfork, Mainnet } from '@ethereumjs/common' | ||
import { RPCStateManager } from '@ethereumjs/statemanager' | ||
import { EOACode7702Tx, type EOACode7702TxData } from '@ethereumjs/tx' | ||
import type { PrefixedHexString } from '@ethereumjs/util' | ||
import { | ||
createAddressFromPrivateKey, | ||
eoaCode7702SignAuthorization, | ||
hexToBytes, | ||
} from '@ethereumjs/util' | ||
import { createVM, runTx } from '@ethereumjs/vm' | ||
import { Interface, parseEther, parseUnits } from 'ethers' | ||
|
||
async function run() { | ||
// ─── your EOA key & address ─────────────────────────────────────────── | ||
const privateKeyHex = '0x1122334455667788112233445566778811223344556677881122334455667788' | ||
const privateKey = hexToBytes(privateKeyHex) | ||
const userAddress = createAddressFromPrivateKey(privateKey) | ||
console.log('EOA:', userAddress.toString()) | ||
|
||
// ─── set up EthereumJS VM with EIP-7702 enabled ─────────────────────── | ||
const common = new Common({ | ||
chain: Mainnet, | ||
hardfork: Hardfork.Cancun, | ||
eips: [7702], | ||
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. While this works, I think for the examples we should just run it on Prague and not on Cancun+7702. 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. Agreed, adjusted |
||
}) | ||
const stateManager = new RPCStateManager({ | ||
provider: 'YourProviderURLHere', | ||
blockTag: 22_000_000n, | ||
}) | ||
const vm = await createVM({ common, stateManager }) | ||
|
||
// ─── constants & ABIs ──────────────────────────────────────────────── | ||
const DAI = '0x6B175474E89094C44Da98b954EedeAC495271d0F' | ||
const UNISWAP_V3_ROUTER = '0xE592427A0AEce92De3Edee1F18E0157C05861564' | ||
const WETH = '0xC02aaa39b223FE8D0A0e5c4F27EaD9083C756Cc2' | ||
const COLD_WALLET = `0x${'42'.repeat(20)}` | ||
const BATCH_CONTRACT = '0xYourBatchContractAddressHere' | ||
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. A note should be placed here what the BATCH_CONTRACT does and the interface. It seems that there is a method which seems to have this type signature: 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. That signature does not seem to be valid. Will add a note. 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. I think a signature which works could be 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. Just seeing this now, but yes I've adjusted to bytes[] address[], fixes the next steps too |
||
|
||
const erc20Abi = [ | ||
'function approve(address _spender, uint256 _amount) external returns (bool success)', | ||
'function transfer(address _to, uint256 _value) public returns (bool success)', | ||
] | ||
const routerAbi = ['function exactInput(bytes)'] | ||
const batchAbi = ['function executeBatch(bytes[] calldata) external'] | ||
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. Oh nvm this interface is here. But it seems that the encoding done in this example is done differently 🤔 |
||
|
||
const erc20 = new Interface(erc20Abi) | ||
const router = new Interface(routerAbi) | ||
const batchContract = new Interface(batchAbi) | ||
|
||
// ─── trade parameters ──────────────────────────────────────────────── | ||
const amountIn = parseUnits('10000', 18) // 10000 DAI | ||
const amountOut = parseEther('4') // expect at least 4 WETH | ||
|
||
// ─── encode your underlying swap data ─────────────────────────────────── | ||
const uniswapV3SwapPayload = `0xYourSwapCallDataHere` | ||
|
||
// ─── encode your three sub-calls ─────────────────────────────────────── | ||
// 1) DAI approve | ||
const callApprove = erc20.encodeFunctionData('approve', [ | ||
UNISWAP_V3_ROUTER, | ||
amountIn, | ||
]) as PrefixedHexString | ||
|
||
// 2) Uniswap V3 swapExactInput | ||
const callSwap = router.encodeFunctionData('exactInput', [ | ||
uniswapV3SwapPayload, | ||
]) as PrefixedHexString | ||
|
||
// 3) sweep WETH to cold wallet | ||
const callTransfer = erc20.encodeFunctionData('transfer', [ | ||
COLD_WALLET, | ||
amountOut, | ||
]) as PrefixedHexString | ||
|
||
const calls = [callApprove, callSwap, callTransfer].map(hexToBytes) | ||
|
||
// ─── sign authorization for each ────── | ||
const targets: PrefixedHexString[] = [DAI, UNISWAP_V3_ROUTER, WETH] | ||
const auths = targets.map((address, i) => | ||
eoaCode7702SignAuthorization({ chainId: '0x1', address, nonce: `0x${i}` }, privateKey), | ||
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. This is incorrect. This will delegate the user EOA first to DAI, then to UNISWAP_V3_ROUTER, and then to WETH (in the same transaction). The net result is that the EOA will point to WETH. We want to point it to the BATCH_CONTRACT, such that if we call into our EOA it will batch-call the targets (as implied by the encoding) 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. Ah you are so right! That's an oversight on my part, adjusting. |
||
) | ||
|
||
// ─── build & send your single 7702 tx ─────────────────────────────── | ||
const batchData = batchContract.encodeFunctionData('executeBatch', [calls]) as `0x${string}` | ||
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. The calldata is mapped to an array (which is fine). How does the |
||
|
||
const txData: EOACode7702TxData = { | ||
nonce: 0n, | ||
gasLimit: 1_000_000n, | ||
maxFeePerGas: parseUnits('10', 9), // 10 gwei | ||
maxPriorityFeePerGas: parseUnits('5', 9), // 5 gwei | ||
to: BATCH_CONTRACT, | ||
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. This will call the batch contract. It will therfore interact with WETH/DAI/Uniswap from the batch contract. We want to interact with it from our EOA (which is why EOA should be delegated to BATCH_CONTRACT) |
||
value: 0n, | ||
data: hexToBytes(batchData), | ||
accessList: [], | ||
authorizationList: auths, | ||
} | ||
|
||
const tx = new EOACode7702Tx(txData, { common }).sign(privateKey) | ||
const { execResult } = await runTx(vm, { tx }) | ||
|
||
console.log( | ||
'🔀 Batch swap DAI→WETH → your wallet:', | ||
execResult.exceptionError ? '❌ Failed' : '✅ Success', | ||
) | ||
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. We could also query the VM here to see the changes (deposit to wallet for instance) |
||
} | ||
|
||
run().catch(console.error) | ||
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.
This does not show how to sign the authorization list item, which is necessary, because the authorization signer does not necessarily have to be the tx signer.
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.
I agree, I just refactored this one initially (didn't actually work on it) so that it had at least correct types. We can certainly improve it.