Skip to content

Instantly share code, notes, and snippets.

@arcticfloyd1984
Last active June 7, 2020 17:00
Show Gist options
  • Save arcticfloyd1984/a71d683dde9c410b5a088134b5b6c146 to your computer and use it in GitHub Desktop.
Save arcticfloyd1984/a71d683dde9c410b5a088134b5b6c146 to your computer and use it in GitHub Desktop.
Contract Wallet approach to execute Uniswap transaction as a meta transaction
require('dotenv').config();
const Biconomy = require("@biconomy/mexa");
const ethers = require('ethers');
const Web3 = require('web3');
const {abi} = require('../abis/abi');
let biconomy;
let web3;
const proxyAddress = "<add_your_own_proxy_address>";
const gnosisSafeAddress = '0x34CfAC646f301356fAa8B21e94227e3583Fe3F5F'; // Gnosis Safe Contract address on Rinkeby
const factoryAddress = '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36'; // Uniswap V1 factory contract address on Rinkeby
var exchangeAddress;
var min_number = 1;
/**
* Get the nonce of proxy contract
* @param {string} proxyAddress
* @returns {number} nonce
*/
const getProxyContractNonce = async function(proxyAddress) {
const proxyContract = new web3.eth.Contract(abi.GnosisSafe, proxyAddress);
const nonce = await proxyContract.methods.nonce().call();
console.log('nonce.toNumber():', nonce);
return nonce;
}
/**
* @param {string} proxyAddress proxy address
* @param {string} to address
* @param {string} destination address
* @param {string} value
* @param {number} nonce
* @param {string} gnosisSafeAddress
* @returns {string} tx hash
*/
const executeSwap = async function(proxyAddress, inputTokenAmount, inputTokenAddress, outputTokenAddress, currentWeb3Account, nonce, gnosisSafeAddress) {
const proxyContract = new web3.eth.Contract(abi.GnosisSafe, proxyAddress);
const factoryContract = new web3.eth.Contract(abi.factoryABI, factoryAddress);
const exchangeAddressResult = await factoryContract.methods.getExchange(inputTokenAddress).call().then(address => {
exchangeAddress = address;
});
const exchangeContract = new web3.eth.Contract(abi.exchangeAbi, exchangeAddress);
// Set parameters of execTransaction()
const valueWei = web3.utils.toWei('0', 'ether');
console.log(valueWei);
// tradeExactTokenForTokensWithData
var _token_sold = String(Math.round(inputTokenAmount*(10**18)))
var _min_token_brought = String(min_number)
var _min_eth_brought = String(min_number)
var _token_address = outputTokenAddress;
// const daiContract = new web3.eth.Contract(abi.daiTokenAbi, inputTokenAddress);
// const daiApprovalData = daiContract.methods.approve(exchangeAddress, '10000000000000000000000000000').encodeABI();
const data = exchangeContract.methods.tokenToTokenSwapInput(_token_sold,_min_token_brought,_min_eth_brought, 1739591241,_token_address).encodeABI();
console.log('Data payload:', data);
const operation = 0; // CALL
const gasPrice = 0; // If 0, then no refund to relayer
const gasToken = '0x0000000000000000000000000000000000000000'; // ETH
const executor = currentWeb3Account;
let txGasEstimate = 0
try {
const gnosisSafeMasterCopy = new web3.eth.Contract(abi.GnosisSafe, gnosisSafeAddress);
const estimateData = gnosisSafeMasterCopy.methods.requiredTxGas(exchangeAddress, valueWei, data, operation).encodeABI();
const estimateResponse = await web3.eth.call({to: proxyAddress, from: proxyAddress, data: estimateData, gasPrice: 0});
txGasEstimate = new web3.utils.BN(estimateResponse.substring(138), 16);
txGasEstimate = txGasEstimate.toNumber() + 10000; // Add 10k else we will fail in case of nested calls
console.log("Safe Tx Gas estimate: " + txGasEstimate);
} catch(e) {
console.log("Could not estimate gas");
}
// Get estimated base gas (Gas costs for that are indipendent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund))
let baseGasEstimate = 0; // If one of the owners executes this transaction it is not really required to set this (so it can be 0) TODO: if using erc20 token
// Create typed data hash
const transactionHash = await proxyContract.methods.getTransactionHash(
exchangeAddress, valueWei, data, operation, txGasEstimate, baseGasEstimate, gasPrice, gasToken, executor, nonce,
).call();
console.log('Transaction hash (typed data):', transactionHash);
const signature = await web3.eth.personal.sign(transactionHash.toString(), currentWeb3Account);
// v + 4
const sig = ethers.utils.splitSignature(signature);
const newSignature = `${sig.r}${sig.s.substring(2)}${Number(sig.v + 4).toString(16)}`;
console.log('Signature 2:', newSignature);
// Call proxy contract to execute Tx
console.log('-----Execute Tx');
proxyContract.methods.execTransaction(
exchangeAddress, valueWei, data, operation, txGasEstimate, baseGasEstimate, gasPrice, gasToken, executor, newSignature,
).send({
"from": currentWeb3Account
}).on('transactionHash', (error, hash) => {
if(error)
console.log(error);
else
console.log('Tx Hash: ', hash);
})
}
const executeUniswapTransaction = async function(inputTokenAddress, outputTokenAddress, inputTokenAmount, currentWeb3Account, web3Provider) {
biconomy = new Biconomy(web3Provider ,{apiKey: "TO9trKq2i.e8b36f77-ec0b-4b3d-917c-dc1c3509c279", debug:true});
web3 = new Web3(biconomy);
console.log(web3);
biconomy.onEvent(biconomy.READY, async() => {
// Initialize your dapp here like getting user accounts etc
console.log("biconomy initialized");
// Get current nonce
const nonce = await getProxyContractNonce(proxyAddress);
console.log('nonce:', nonce);
// Execute tx
await executeSwap(
proxyAddress,
inputTokenAmount,
inputTokenAddress,
outputTokenAddress,
currentWeb3Account,
nonce,
gnosisSafeAddress
);
}).onEvent(biconomy.ERROR, (error, message) => {
console.log(error,message)
// Handle error while initializing mexa
});
}
export default executeUniswapTransaction;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment