Skip to content

Instantly share code, notes, and snippets.

@livingrock7
Created June 1, 2021 18:07
Show Gist options
  • Save livingrock7/4f5801b29f0ef18f018aa5909df42a54 to your computer and use it in GitHub Desktop.
Save livingrock7/4f5801b29f0ef18f018aa5909df42a54 to your computer and use it in GitHub Desktop.
oddz | personal sign EIP2771 web3
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