Connecting Web2 & Web3 — Account Abstraction (Part II)
In the preceding article, we explored creating a wallet using Account Abstraction (AA). Building on that, we are now delving into invoking another smart contract from the AA wallet.
Our focus in this next phase will be on a specific use case, where we’ll tackle the transfer of an ERC20 token, specifically USDC (USD Coin), to a different wallet. Understanding how to call another contract from the AA wallet opens up new possibilities for decentralized applications (DApps) and smart contract interactions.
By the end of this tutorial, readers will have a solid grasp of how to create and manage AA wallets and interact with external smart contracts seamlessly.
Preparation Of CallData
module.exports = {
getUsdcTransfer: async (reciever, value) => {
const usdcParams = web3.eth.abi.encodeParameters(
["address", "uint256"],
[reciever, value]
);
const usdcTransferData = `0xa9059cbb${usdcParams.slice(2)}`;
const params = web3.eth.abi.encodeParameters(
["address", "uint256", "bytes"],
["0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", 0, usdcTransferData]
);
const callData = `0xb61d27f6${params.slice(2)}`;
return callData;
},
};
The provided code prepares data for transferring the USDC token (0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) to the designated receiver. This data is subsequently encoded based on the Simple Account's ‘execute’ method (0xb61d27f6).
It is important to highlight that the code currently includes ‘0’ as the second parameter while encoding it into uint256. This specific value signifies the transaction value, which can be customized according to the use case requirements.
Please proceed with caution when altering the transaction value, ensuring its alignment with the intended transfer amount, and verifying the smart contract’s specifications to prevent unintended consequences or errors in token transfers.
Including the CallData in UserOps and sending it to Bundler
const sender = await getSender(
config.testData.account1PublicKey,
config.testData.salt
);
const callData = await getUsdcTransfer(
'0x7b2a36411a82fd51de5d47e104d4b8025685748b',
50
);
const entryPointNonce = await getNonce(sender);
const prePaymasterAndData = await getPrePaymasterAndData();
const userOps = {
sender,
nonce: entryPointNonce,
initCode: "0x",
callData,
callGasLimit: 260611,
gasLimit: 362451,
verificationGasLimit: 362451,
preVerificationGas: 53576,
maxFeePerGas: 29964445250,
maxPriorityFeePerGas: 100000000,
paymasterAndData: "0x",
signature: "0x",
};
As we have seen in the previous article, this userOps object can be signed and sent to the bundler for execution.
Wider Usecase
We explored a use case involving the transfer of USDC to another wallet. However, what if users desire more complex operations, such as performing swaps on Uniswap v2 or v3 or engaging in lending activities on platforms like AAVE and Compound?
When faced with such complex tasks, developers have two approaches to consider:
Approach 1 involves understanding the inner workings of each protocol and constructing the necessary call data accordingly. However, this method can be immensely challenging and time-consuming, requiring a comprehensive grasp of multiple protocols.
On the other hand, Approach 2 offers a more efficient solution. By leveraging expand.network, developers can conveniently prepare the required CallData for various operations. By utilizing expand.network, developers gain a powerful tool that simplifies the interaction with different DeFi protocols, enabling them to focus on building innovative applications without being bogged down by the complexities of protocol integration. This approach is a time-saving and effective solution for engaging in various DeFi activities easily and efficiently.
For instance, consider the example of swapping USDC to WETH on Uniswap v2.
module.exports = {
swapUsdcToWeth: async (value, sender) => {
const url = "https://api.expand.network/dex/swap";
const data = JSON.stringify({
dexId: "1000",
amountIn: value.toString(),
amountOutMin: "0",
path: [USDC, WETH],
to: sender,
deadline: (Math.floor(Date.now() / 1000) + 24 * 60 * 60).toString(),
from: sender,
gas: "173376",
});
const headers = {
"Content-Type": "application/json",
"x-api-key": "<your api key>",
};
const response = await axios.post(url, data, { headers });
const params = web3.eth.abi.encodeParameters(
["address", "uint256", "bytes"],
[response.data.data.to, 0, response.data.data.data]
);
const callData = `0xb61d27f6${params.slice(2)}`;
return callData;
},
};
Hurray, with this, we can finally interact with the other contracts.
Next Steps
In the next part of this series, we will go through the following:-
- Let the paymaster pay for our Gas!!
References
- Why we need wide adoption of social recovery wallets
- Account Abstraction by Metamask
- ERC 4337: Account Abstraction Using Alt Mempool
I’d love to hear any feedback or questions. You can ask the question by commenting, and I will try my best to answer it.