Last active
June 7, 2020 17:00
-
-
Save arcticfloyd1984/a71d683dde9c410b5a088134b5b6c146 to your computer and use it in GitHub Desktop.
Contract Wallet approach to execute Uniswap transaction as a meta transaction
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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