execTransactionsFromModule
for batch transactions
Dependency
In this example, we are using TypeScript
as the programming language. Please install the following dependencies first:
@ethereumjs/tx
@ethereumjs/common
Let's simulate the "Remove Liquidity" function on Uniswap on the Goerli network, removing liquidity for UNI-WETH pair.
We will use the decreaseLiquidity
and collect
method of the NonfungiblePositionManager contract. Please first set the Role and Member. (for the detailed configurations for methods and parameters, you may refer to the Best Practice of Uniswap)
Example
We use Infura as the provider. You may choose the provider you like:
import Web3 from 'web3'
const moduleAddress = 'YOUR_COMPASS_SAFE_MODULE_ADDRESS'
const safeAddress = 'YOUR_GNOSIS_SAFE_ADDRESS'
const INFURA_KEY = 'YOUR_INFURA_PROJECT_ID'
const provider = new Web3.providers.HttpProvider(`https://goerli.infura.io/v3/${INFURA_KEY}`)
const senderAddress = 'YOUR_MEMBER_ADDRESS'
const privateKey = Buffer.from('YOUR_MEMBER_PRIVATE_KEY', 'hex')
Execute the execTransactionsFromModule
method of the Compass Safe Module:
import { AbiItem } from 'web3-utils'
import { TransactionReceipt } from 'web3-core'
import { Transaction } from '@ethereumjs/tx'
import { Chain, Common } from '@ethereumjs/common'
import { ethers } from 'ethers'
enum Operation {
None,
Call,
DelegateCall,
Both,
}
interface Call {
roleName: string,
to: string,
value: string
data: string,
operation: Operation,
}
const execTransactionsFromModule = async (
calls: Call[],
): Promise<TransactionReceipt> => {
const web3 = new Web3(provider)
const abi = [{
"inputs": [
{
"components": [
{
"internalType": "bytes32",
"name": "roleName",
"type": "bytes32"
},
{
"internalType": "address",
"name": "to",
"type": "address"
},
{
"internalType": "uint256",
"name": "value",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
},
{
"internalType": "enum Operation",
"name": "operation",
"type": "uint8"
}
],
"internalType": "struct SafeModule.Exec[]",
"name": "execs",
"type": "tuple[]"
}
],
"name": "execTransactionsFromModule",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},]
const contract = new web3.eth.Contract(abi as AbiItem[], moduleAddress)
const encodedData = contract.methods['execTransactionsFromModule'](calls).encodeABI()
const nonce = await web3.eth.getTransactionCount(senderAddress)
const rawTx = {
nonce,
to: moduleAddress,
data: encodedData,
gasPrice: 2162230808,
gasLimit: 256312,
value: 0,
}
const common = new Common({ chain: Chain.Goerli })
const tx = Transaction.fromTxData(rawTx, { common })
const signedTx = tx.sign(privateKey)
const serializedTx = signedTx.serialize()
return (
web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'))
.once('sending', function (payload) { console.log('sending transaction') })
.once('sent', function (payload) { console.log('sent transaction') })
.once('transactionHash', function (hash) { console.log('transactionHash: ', hash) })
)
}
Please note: to obtain the parameter roleName, we can directly retrieve it from the role list in Compass Safe, or compile it based on the Role Name.
const name = "Uniswap V3 Mint"
const roleName = ethers.utils.formatBytes32String(name)
Use execTransactionsFromModule
to call decreaseLiquidity
and collect
method
const decreaseLiquidity = async () => {
const UNI = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
const EMPTY_ADDRESS = '0x0000000000000000000000000000000000000000'
const uniswapV3NftManagerAddress = '0xc36442b4a4522e871399cd717abdd847ab11fe88'
const abi = [
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint128",
"name": "liquidity",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "DecreaseLiquidity",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount0",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amount1",
"type": "uint256"
}
],
"name": "Collect",
"type": "event"
},
]
const web3 = new Web3(provider)
const uniswapV3NftManagerContract = new web3.eth.Contract(abi as AbiItem[], uniswapV3NftManagerAddress)
const deadline = Math.floor(Date.now() / 1000) + 60 * 10; // 10 minutes from now
const decreaseLiquidityParams = [
60162,// tokenId The id of the erc721 token
4999000099994999900,
2499999999999999,
0,
deadline,
]
const decreaseLiquidityData = uniswapV3NftManagerContract.methods['decreaseLiquidity'](
decreaseLiquidityParams[0],
decreaseLiquidityParams[1],
decreaseLiquidityParams[2],
decreaseLiquidityParams[3],
decreaseLiquidityParams[4],
).encodeABI();
const amount0Max = ethers.utils.parseUnits("100", 18);
const amount1Max = ethers.utils.parseUnits("100", 18);
const collectParams = [
60162,// tokenId The id of the erc721 token
EMPTY_ADDRESS,
amount0Max,
amount1Max,
]
const collectData = uniswapV3NftManagerContract.methods['collect'](
collectParams[0],
collectParams[1],
collectParams[2],
collectParams[3],
).encodeABI();
const name = 'YOUR_ROLE_NAME'
const roleName = ethers.utils.formatBytes32String(name);
const calls = [
{
roleName,
to: uniswapV3NftManagerAddress,
value: '0',
data: decreaseLiquidityData,
operation: Operation.Call,
},
{
roleName,
to: uniswapV3NftManagerAddress,
value: '0',
data: collectData,
operation: Operation.Call,
},
]
const receipt = await execTransactionsFromModule(calls)
console.log('receipt: ', receipt)
}
You can view the transaction on https://goerli.etherscan.io/ using the transactionHash
.
Last updated