Created
June 1, 2021 18:07
-
-
Save livingrock7/4f5801b29f0ef18f018aa5909df42a54 to your computer and use it in GitHub Desktop.
oddz | personal sign EIP2771 web3
This file contains hidden or 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
import React, { useState, useEffect } from "react"; | |
import "../App.css"; | |
import Button from "@material-ui/core/Button"; | |
import { | |
NotificationContainer, | |
NotificationManager | |
} from "react-notifications"; | |
import "react-notifications/lib/notifications.css"; | |
import Backdrop from '@material-ui/core/Backdrop'; | |
import CircularProgress from '@material-ui/core/CircularProgress'; | |
import Web3 from "web3"; | |
import { ethers } from "ethers"; | |
import { Biconomy } from "@biconomy/mexa"; | |
import { makeStyles } from '@material-ui/core/styles'; | |
import Link from '@material-ui/core/Link'; | |
import Typography from '@material-ui/core/Typography'; | |
import { Box } from "@material-ui/core"; | |
let sigUtil = require("eth-sig-util"); | |
let config = { | |
contract: { | |
address: "0x9E4995d87f4CC845D074967f5E426B249F2fA526", | |
abi: [{"inputs":[{"internalType":"contractIOddzOption","name":"_optionManager","type":"address"},{"internalType":"address","name":"_trustedForwarder","type":"address"},{"internalType":"contractIERC20","name":"_oddzToken","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_month","type":"uint256"},{"indexed":true,"internalType":"address","name":"_provider","type":"address"}],"name":"OptionProvider","type":"event"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"allocateOddzReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_pair","type":"address"},{"internalType":"bytes32","name":"_optionModel","type":"bytes32"},{"internalType":"uint256","name":"_premiumWithSlippage","type":"uint256"},{"internalType":"uint256","name":"_expiration","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_strike","type":"uint256"},{"internalType":"enumIOddzOption.OptionType","name":"_optionType","type":"uint8"},{"internalType":"address","name":"_provider","type":"address"}],"name":"buy","outputs":[{"internalType":"uint256","name":"optionId","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_providers","type":"address[]"},{"internalType":"uint256","name":"_month","type":"uint256"}],"name":"distributeReward","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_pair","type":"address"},{"internalType":"bytes32","name":"_optionModel","type":"bytes32"},{"internalType":"uint256","name":"_expiration","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint256","name":"_strike","type":"uint256"},{"internalType":"enumIOddzOption.OptionType","name":"_optionType","type":"uint8"}],"name":"getPremium","outputs":[{"internalType":"uint256","name":"optionPremium","type":"uint256"},{"internalType":"uint256","name":"txnFee","type":"uint256"},{"internalType":"uint256","name":"iv","type":"uint256"},{"internalType":"uint8","name":"ivDecimal","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"forwarder","type":"address"}],"name":"isTrustedForwarder","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"oddzToken","outputs":[{"internalType":"contractIERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"optionManager","outputs":[{"internalType":"contractIOddzOption","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"premiumCollected","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"forwarderAddress","type":"address"}],"name":"setTrustedForwarder","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"totalOddzAllocated","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"totalPremiumCollected","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"trustedForwarder","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"usersForTheMonth","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"versionRecipient","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}] | |
}, | |
apiKey: { | |
test: "2S4eaf2_V.0dedd87f-4191-4d6d-b346-d61c390850fb", | |
prod: "8nvA_lM_Q.0424c54e-b4b2-4550-98c5-8b437d3118a9" | |
} | |
} | |
config.usdc = { | |
address: "0x81090433945cbd56383ace1e70528865d2b0bc0e", | |
abi: [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}]}; | |
let web3, biconomy; | |
let contract, usdcToken; | |
const useStyles = makeStyles((theme) => ({ | |
root: { | |
'& > * + *': { | |
marginLeft: theme.spacing(2), | |
}, | |
}, | |
link: { | |
marginLeft: "5px" | |
}, | |
backdrop: { | |
zIndex: theme.zIndex.drawer + 1, | |
color: '#fff', | |
opacity: '.85!important', | |
background: '#000' | |
}, | |
})); | |
function App() { | |
const classes = useStyles(); | |
const [backdropOpen, setBackdropOpen] = React.useState(true); | |
const [loadingMessage, setLoadingMessage] = React.useState(" Loading Application ..."); | |
const [quote, setQuote] = useState("This is a default quote"); | |
const [owner, setOwner] = useState("Default Owner Address"); | |
const [newQuote, setNewQuote] = useState(""); | |
const [selectedAddress, setSelectedAddress] = useState(""); | |
const [metaTxEnabled] = useState(true); | |
const [transactionHash, setTransactionHash] = useState(""); | |
useEffect(() => { | |
async function init() { | |
if ( | |
typeof window.ethereum !== "undefined" && | |
window.ethereum.isMetaMask | |
) { | |
// Ethereum user detected. You can now use the provider. | |
const provider = window["ethereum"]; | |
await provider.enable(); | |
let kovanProvider = new Web3.providers.HttpProvider("https://kovan.infura.io/v3/d126f392798444609246423b06116c77"); | |
setLoadingMessage("Initializing Biconomy ..."); | |
biconomy = new Biconomy(provider, { | |
walletProvider: window.ethereum, | |
apiKey: config.apiKey.test, | |
debug: true | |
}); | |
// This web3 instance is used to read normally and write to contract via meta transactions. | |
web3 = new Web3(biconomy); | |
biconomy.onEvent(biconomy.READY, () => { | |
// Initialize your dapp here like getting user accounts etc | |
contract = new web3.eth.Contract( | |
config.contract.abi, | |
config.contract.address | |
); | |
usdcToken = new ethers.Contract( | |
config.usdc.address, | |
config.usdc.abi, | |
biconomy.getSignerByAddress("0x9AAFe3E7E4Fe0E15281831f7D2f33eFfE18Fc7d5") | |
); | |
setSelectedAddress(provider.selectedAddress); | |
//getQuoteFromNetwork(); | |
provider.on("accountsChanged", function (accounts) { | |
setSelectedAddress(accounts[0]); | |
}); | |
}).onEvent(biconomy.ERROR, () => { | |
// Handle error while initializing mexa | |
}); | |
} else { | |
showErrorMessage("Metamask not installed"); | |
} | |
} | |
init(); | |
}, []); | |
const handleClose = () => { | |
setBackdropOpen(false); | |
}; | |
const onQuoteChange = event => { | |
setNewQuote(event.target.value); | |
}; | |
const getExpiry = (days = 1) => { | |
return 60 * 60 * 24 * days; | |
}; | |
const OptionType = { | |
Call: 0, | |
Put: 1, | |
}; | |
const onSubmitWithPrivateKey = async () => { | |
if (newQuote != "" && contract) { | |
setTransactionHash(""); | |
console.log("Sending meta transaction"); | |
let privateKey = "2ef295b86aa9d40ff8835a9fe852942ccea0b7c757fad5602dfa429bcdaea910"; | |
let userAddress = "0xE1E763551A85F04B4687f0035885E7F710A46aA6"; | |
let txParams = { | |
"from": userAddress, | |
"to": config.contract.address, | |
"data": contract.methods.setQuote(newQuote).encodeABI(), | |
"gasLimit": web3.utils.toHex(300000), | |
}; | |
const signedTx = await web3.eth.accounts.signTransaction(txParams, `0x${privateKey}`); | |
const forwardData = await biconomy.getForwardRequestAndMessageToSign(signedTx.rawTransaction); | |
console.log(forwardData); | |
console.log(privateKey); | |
// const signature = sigUtil.personalSign(new Buffer.from(privateKey, 'hex'), { data: forwardData.personalSignatureFormat }); | |
let {signature} = web3.eth.accounts.sign("0x" + forwardData.personalSignatureFormat.toString("hex"), privateKey); | |
let rawTransaction = signedTx.rawTransaction; | |
let data = { | |
signature: signature, | |
forwardRequest: forwardData.request, | |
rawTransaction: rawTransaction, | |
signatureType: biconomy.PERSONAL_SIGN | |
}; | |
web3.eth.sendSignedTransaction(data) | |
.on('transactionHash', (hash) => { | |
console.log(`Transaction hash is ${hash}`) | |
showInfoMessage(`Transaction sent via Biconomy. Waiting for confirmation.`); | |
}) | |
.once('confirmation', (confirmation, receipt) => { | |
console.log(`Transaction Confirmed.`); | |
console.log(receipt); | |
setTransactionHash(receipt.transactionHash); | |
showSuccessMessage("Transaction confirmed"); | |
//getQuoteFromNetwork(); | |
}); | |
} else { | |
showErrorMessage("Please enter the quote"); | |
} | |
} | |
const onSubmit = async () => { | |
if (newQuote != "" && contract) { | |
setTransactionHash(""); | |
const usdcTokenAddress = "0x81090433945cbd56383ace1e70528865d2b0bc0e"; | |
const optionSdkAddress = "0x9E4995d87f4CC845D074967f5E426B249F2fA526"; | |
const optionManagerAddress = "0x0a6A99d4A65E03A6650F77dC2e30759A377c8806"; | |
if (metaTxEnabled) { | |
debugger; | |
const totalSupply = await usdcToken.totalSupply(); | |
let txn = await usdcToken.approve(optionManagerAddress, totalSupply); | |
await txn.wait(1); | |
let tx = contract.methods.buy( | |
"0xfcb06d25357ef01726861b30b0b83e51482db417", | |
ethers.utils.formatBytes32String("B_S"), | |
ethers.BigNumber.from(ethers.utils.parseEther("10000000")), | |
getExpiry(1), | |
ethers.BigNumber.from(ethers.utils.parseEther("1")), | |
ethers.BigNumber.from(348100000000), | |
OptionType.Call, | |
"0x0819BBae96c2C0F15477D212e063303221Cf24b9", | |
).send({ | |
from: selectedAddress, | |
signatureType: biconomy.PERSONAL_SIGN | |
}); | |
tx.on("transactionHash", function (hash) { | |
console.log(`Transaction hash is ${hash}`); | |
showInfoMessage(`Transaction sent. Waiting for confirmation ..`); | |
}).once("confirmation", function (confirmationNumber, receipt) { | |
console.log(receipt); | |
setTransactionHash(receipt.transactionHash); | |
showSuccessMessage("Transaction confirmed on chain"); | |
//getQuoteFromNetwork(); | |
}); | |
} else { | |
console.log("Sending normal transaction"); | |
contract.methods | |
.setQuote(newQuote) | |
.send({ from: selectedAddress }) | |
.on("transactionHash", function (hash) { | |
showInfoMessage(`Transaction sent to blockchain with hash ${hash}`); | |
}) | |
.once("confirmation", function (confirmationNumber, receipt) { | |
setTransactionHash(receipt.transactionHash); | |
showSuccessMessage("Transaction confirmed"); | |
//getQuoteFromNetwork(); | |
}); | |
} | |
} else { | |
showErrorMessage("Please enter the quote"); | |
} | |
}; | |
const getQuoteFromNetwork = () => { | |
setLoadingMessage("Getting Quote from contact ..."); | |
try { | |
if (web3 && contract) { | |
contract.methods | |
.getQuote() | |
.call() | |
.then(function (result) { | |
handleClose(); | |
console.log(result); | |
if ( | |
result && | |
result.currentQuote != undefined && | |
result.currentOwner != undefined | |
) { | |
if (result.currentQuote == "") { | |
showErrorMessage("No quotes set on blockchain yet"); | |
} else { | |
setQuote(result.currentQuote); | |
setOwner(result.currentOwner); | |
} | |
} else { | |
showErrorMessage("Not able to get quote information from Network"); | |
} | |
}); | |
} else { | |
handleClose(); | |
} | |
} catch (error) { | |
handleClose(); | |
console.log(error); | |
} | |
}; | |
const showErrorMessage = message => { | |
NotificationManager.error(message, "Error", 5000); | |
}; | |
const showSuccessMessage = message => { | |
NotificationManager.success(message, "Message", 3000); | |
}; | |
const showInfoMessage = message => { | |
NotificationManager.info(message, "Info", 3000); | |
}; | |
return ( | |
<div className="App"> | |
<section className="top-row"> | |
<div className="top-row-item"> | |
<span className="label">Library </span> | |
<span className="label-value">web3.js</span> | |
</div> | |
<div className="top-row-item"> | |
<span className="label">Meta Transaction</span> | |
<span className="label-value">EIP-2771</span> | |
</div> | |
<div className="top-row-item"> | |
<span className="label">Signature Type</span> | |
<span className="label-value">Personal Signature</span> | |
</div> | |
</section> | |
<section className="main"> | |
<div className="mb-wrap mb-style-2"> | |
<blockquote cite="http://www.gutenberg.org/ebboks/11"> | |
<p>{quote}</p> | |
</blockquote> | |
</div> | |
<div className="mb-attribution"> | |
<p className="mb-author">{owner}</p> | |
{selectedAddress.toLowerCase() === owner.toLowerCase() && ( | |
<cite className="owner">You are the owner of the quote</cite> | |
)} | |
{selectedAddress.toLowerCase() !== owner.toLowerCase() && ( | |
<cite>You are not the owner of the quote</cite> | |
)} | |
</div> | |
</section> | |
<section> | |
{transactionHash !== "" && <Box className={classes.root} mt={2} p={2}> | |
<Typography> | |
Check your transaction hash | |
<Link href={`https://kovan.etherscan.io/tx/${transactionHash}`} target="_blank" | |
className={classes.link}> | |
here | |
</Link> | |
</Typography> | |
</Box>} | |
</section> | |
<section> | |
<div className="submit-container"> | |
<div className="submit-row"> | |
<input | |
type="text" | |
placeholder="Enter your quote" | |
onChange={onQuoteChange} | |
value={newQuote} | |
/> | |
<Button variant="contained" color="primary" onClick={onSubmit}> | |
Submit | |
</Button> | |
<Button variant="contained" color="primary" onClick={onSubmitWithPrivateKey} style={{ marginLeft: "10px" }}> | |
Submit (using private key) | |
</Button> | |
</div> | |
</div> | |
</section> | |
<Backdrop className={classes.backdrop} open={backdropOpen} onClick={handleClose}> | |
<CircularProgress color="inherit" /> | |
<div style={{ paddingLeft: "10px" }}>{loadingMessage}</div> | |
</Backdrop> | |
<NotificationContainer /> | |
</div> | |
); | |
} | |
export default App; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment