Skip to content

Commit

Permalink
Add support for gasless relayers, test xDai deploy
Browse files Browse the repository at this point in the history
  • Loading branch information
nalinbhardwaj committed Jan 27, 2022
1 parent 1b7e7c3 commit 26ddc41
Show file tree
Hide file tree
Showing 8 changed files with 114 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,31 @@ import "./airdropVerifier.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// Largely inspired by https://github.com/Uniswap/merkle-distributor/blob/master/contracts/interfaces/IMerkleDistributor.sol
contract ZKT is ERC20, Verifier {
contract SDT is ERC20, Verifier {
uint256 constant SNARK_FIELD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;

uint256 public merkleRoot;
mapping(uint256 => bool) public claimedNullifiers;
string public messageClaimString = 'zk-airdrop';
uint256 messageClaimHash = 0x52a0832a7b7b254efb97c30bb6eaea30ef217286cba35c8773854c8cd41150de;
uint256[3] public messageClaimHash;
event Claim(address indexed claimant, uint256 amount);

/**
* @dev Constructor.
* @param freeSupply The number of tokens to issue to the contract deployer.
* @param airdropSupply The number of tokens to reserve for the airdrop.
* @param _merkleRoot Merkle Root of the Airdrop addresses.
* @param _messageClaimHash Claim message hash
*/
constructor(
uint256 freeSupply,
uint256 airdropSupply,
uint256 _merkleRoot
) public ERC20("Zero Knowledge Token", "ZKT") {
uint256 _merkleRoot,
uint256[3] memory _messageClaimHash
) public ERC20("StealthDrop Token", "SDT") {
_mint(msg.sender, freeSupply);
_mint(address(this), airdropSupply);
merkleRoot = _merkleRoot;
messageClaimHash = _messageClaimHash;
}

/**
Expand All @@ -42,25 +46,38 @@ contract ZKT is ERC20, Verifier {
uint256[2] memory c,
uint256[6] memory signals
) external {
// TODO indices
require(
signals[0] == merkleRoot,
"Merkle Root does not match contract"
);

require(
!claimedNullifiers[signals[5]],
"Nullifier has already been claimed"
);
require(
signals[4] == uint256(msg.sender),
"Sender address does not match zk input sender address"
);
require(signals[1] == 0x000000000000000000000000000000000000000000235c8773854c8cd41150de, "Message hash invalid"); // TODO
require(signals[2] == 0x0000000000000000000000000000000000000000000c2edbaba8c3bc85ca1b2e, "Message hash invalid"); // TODO
require(signals[3] == 0x000000000000000000000000000000000000000000052a0832a7b7b254efb97c, "Message hash invalid"); // TODO
require(verifyProof(a, b, c, signals), "Invalid Proof");
claimedNullifiers[signals[0]] = true;
emit Claim(msg.sender, 10**18);
_transfer(address(this), msg.sender, 10**18);
require(uint256(signals[5]) < SNARK_FIELD ,"Nullifier is not within the field");
claimedNullifiers[signals[5]] = true;

for(uint i = 0;i < messageClaimHash.length;i++) {
require(signals[i + 1] == messageClaimHash[i], "claim message hash does not match");
}

// require(verifyProof(a, b, c, signals), "Invalid Proof");

This comment has been minimized.

Copy link
@nalinbhardwaj

nalinbhardwaj Jan 27, 2022

Author Collaborator

oop todo


emit Claim(address(uint160(signals[4])), 10**18);
_transfer(address(this), address(uint160(signals[4])), 10**18);
msg.sender.transfer(getChestQuota());
}

function getChestBalance() public view returns (uint256) {
return address(this).balance;
}

function getChestQuota() public view returns (uint256) {
return 10**18 * address(this).balance / balanceOf(address(this));
}

function depositChest(uint256 amount) payable public {
require(msg.value == amount, "Suggested amount does not match value");
}
}
45 changes: 30 additions & 15 deletions zkaffold-eth/packages/hardhat/contracts/airdropVerifier.sol
Original file line number Diff line number Diff line change
Expand Up @@ -194,31 +194,46 @@ contract Verifier {
8495653923123431417604973247489272438418190587263600148770280649306958101930]
);
vk.delta2 = Pairing.G2Point(
[19130726750476098912074637514736563975639903443377211535352050757646810877625,
1561016405148904428327674756813293775640482327979702522010679425336848667052],
[6235895961424132570969577427375589445018807194135979897498720410274744503228,
16412966203195659905317500707222897105704344328929590460846898308986856619523]
[11559732032986387107991004021392285783925812861821192530917403151452391805634,
10857046999023057135944570762232829481370756359578518086990519993285655852781],
[4082367875863433681332203403145435568316851327593401208105741076214120093531,
8495653923123431417604973247489272438418190587263600148770280649306958101930]
);
vk.IC = new Pairing.G1Point[](4);
vk.IC = new Pairing.G1Point[](7);

vk.IC[0] = Pairing.G1Point(
13592975421652748383505452244244522547583970959603069115693225750249893657966,
565176555528992751758868423201375153183903712919183530497569482970580636490
19288165728257403768609860799962056600911780190108798429594667547266359193396,
16354061724444805293012468382789827040510885742786945246182871339271613453995
);

vk.IC[1] = Pairing.G1Point(
16048861765025148365460151811456733945176324794751544846883944888004668734422,
21762054117635607383535274847087904999021459545584078008748654958776378762975
981414790976824110676592986673309564887894259887504869882029951520358196127,
2785867579417429044339582473749703457826489773805593341787758499278980217126
);

vk.IC[2] = Pairing.G1Point(
4365379751064059758588464342607223174344924531517888604973842712374877717285,
13907875362279543347871649897349003768632144864807331645758318912702515922582
14663980406345247812418850591751430009218400450991585434860982077572260645408,
1071501421395049012104550421840764038642882494116029768293559539331958967138
);

vk.IC[3] = Pairing.G1Point(
7793402079868059874998638884410070872255020101554809883168526162204863115150,
8459607618222222354072341798065886142609155680173547782499718708601830307982
7044854376187817779938346296513890429472519195496415784534206413809437360384,
19387807888291509064255342945325671302632660248636182130963775578524648248439
);

vk.IC[4] = Pairing.G1Point(
8374195671778080102996748585088435162100123737779232067883781215404782986005,
2689655265460635883623359688982260230953329888687040987390588484916169946972
);

vk.IC[5] = Pairing.G1Point(
7481560312381256884743746147965956995360571532025616345046881594756769686071,
14317116877877981244140699558170463679570554820507788772557547605754283894114
);

vk.IC[6] = Pairing.G1Point(
20448755501684433877195343178287198057523177445520259103250350476568421980751,
510455179843187740236335973469133450588669020934488364445260908378959177813
);

}
Expand Down Expand Up @@ -246,7 +261,7 @@ contract Verifier {
uint[2] memory a,
uint[2][2] memory b,
uint[2] memory c,
uint[3] memory input
uint[6] memory input
) public view returns (bool r) {
Proof memory proof;
proof.A = Pairing.G1Point(a[0], a[1]);
Expand All @@ -262,4 +277,4 @@ contract Verifier {
return false;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
// deploy/00_deploy_your_contract.js
// deprecated

module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
await deploy("ZKT", {
await deploy("SDT", {
// Learn more about args here: https://www.npmjs.com/package/hardhat-deploy#deploymentsdeploy
from: deployer,
args: ["0x123", "0x111", "0x24037BA1BD610912C8ED2466921FD5470B871B8F60D7D54A48BC20EBD9E0E38E", "zk-airdrop"],
args: ["0x123", "0x111", "0x24037BA1BD610912C8ED2466921FD5470B871B8F60D7D54A48BC20EBD9E0E38E", ["0x000000000000000000000000000000000000000000235c8773854c8cd41150de", "0x0000000000000000000000000000000000000000000c2edbaba8c3bc85ca1b2e", "0x000000000000000000000000000000000000000000052a0832a7b7b254efb97c"]],
log: true,
});

Expand Down
2 changes: 1 addition & 1 deletion zkaffold-eth/packages/hardhat/hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const { isAddress, getAddress, formatUnits, parseUnits } = utils;
//
// Select the network you want to deploy to here:
//
const defaultNetwork = "ropsten";
const defaultNetwork = "xdai";

function mnemonic() {
try {
Expand Down
4 changes: 2 additions & 2 deletions zkaffold-eth/packages/hardhat/scripts/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const R = require("ramda");
const main = async () => {
console.log("\n\n 📡 Deploying...\n");

const yourCollectible = await deploy("ZKT") // <-- add in constructor args like line 19 vvvv
const yourCollectible = await deploy("SDT") // <-- add in constructor args like line 19 vvvv

//const yourContract = await ethers.getContractAt('YourContract', "0xaAC799eC2d00C013f1F11c37E654e59B0429DF6A") //<-- if you want to instantiate a version of a contract at a specific address!
//const secondContract = await deploy("SecondContract")
Expand Down Expand Up @@ -64,7 +64,7 @@ const deploy = async (
) => {
console.log(` 🛰 Deploying: ${contractName}`);

const contractArgs = ["1000000000000000000", "100000000000000000000", "10820035775911352619808711599990561234314755649544548352074412739114305203484"];
const contractArgs = ["1000000000000000000", "100000000000000000000", "10820035775911352619808711599990561234314755649544548352074412739114305203484", ["0x000000000000000000000000000000000000000000235c8773854c8cd41150de", "0x0000000000000000000000000000000000000000000c2edbaba8c3bc85ca1b2e", "0x000000000000000000000000000000000000000000052a0832a7b7b254efb97c"]];
const contractArtifacts = await ethers.getContractFactory(contractName, {
libraries: libraries,
});
Expand Down
16 changes: 10 additions & 6 deletions zkaffold-eth/packages/react-app/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { formatEther, parseEther } from "@ethersproject/units";
import { Subgraph } from "./views";
import { INFURA_ID, DAI_ADDRESS, DAI_ABI, NETWORK, NETWORKS } from "./constants";
import Withdraw from "./components/Withdraw";
import FaucetHint from "./components/Faucet";
import NewAirdrop from "./components/NewAirdrop";
import { ethers } from "ethers";
/*
Expand All @@ -46,7 +47,7 @@ import { ethers } from "ethers";
*/

/// 📡 What chain are your contracts deployed to?
const targetNetwork = NETWORKS["ropsten"]; // <------- select your target frontend network (localhost, rinkeby, xdai, mainnet)
const targetNetwork = NETWORKS["xdai"]; // <------- select your target frontend network (localhost, rinkeby, xdai, mainnet)

// 😬 Sorry for all the console logging
const DEBUG = true;
Expand Down Expand Up @@ -300,7 +301,7 @@ function App(props) {
*/}

<Contract
name="ZKT"
name="SDT"
signer={userProvider.getSigner()}
provider={localProvider}
address={address}
Expand All @@ -327,6 +328,8 @@ function App(props) {
logoutOfWeb3Modal={logoutOfWeb3Modal}
mainnetProvider={mainnetProvider}
provider={userProvider}
transactor={tx}
gasPrice={gasPrice}
/>
</Route>
<Route exact path="/airdrop">
Expand All @@ -340,7 +343,7 @@ function App(props) {
</Switch>
</BrowserRouter>

{/* 👨‍💼 Your account is in the top right with a wallet at connect options
{ /* 👨‍💼 Your account is in the top right with a wallet at connect options
<div style={{ position: "fixed", textAlign: "right", right: 0, top: 0, padding: 10 }}>
<Account
address={address}
Expand All @@ -353,10 +356,11 @@ function App(props) {
logoutOfWeb3Modal={logoutOfWeb3Modal}
blockExplorer={blockExplorer}
/>
</div> */}
<FaucetHint localProvider={localProvider} targetNetwork={targetNetwork} address={address} />
</div>
{/* 🗺 Extra UI like gas price, eth price, faucet, and support: */}
{/* <div style={{ position: "fixed", textAlign: "left", left: 0, bottom: 20, padding: 10 }}>
🗺 Extra UI like gas price, eth price, faucet, and support:
<div style={{ position: "fixed", textAlign: "left", left: 0, bottom: 20, padding: 10 }}>
<Row align="middle" gutter={[4, 4]}>
<Col span={8}>
<Ramp price={price} address={address} networks={NETWORKS} />
Expand Down
56 changes: 33 additions & 23 deletions zkaffold-eth/packages/react-app/src/components/Withdraw.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,14 @@ import { useContractLoader } from "../hooks";
import { Transactor } from "../helpers";
import { CheckCircle, Circle, GitHub, XCircle } from "react-feather";
import { useLookupAddress } from "../hooks";
import { useGasPrice } from "eth-hooks";

const signText = "zk-airdrop";
const signTextHash = "0x52a0832a7b7b254efb97c30bb6eaea30ef217286cba35c8773854c8cd41150de";

const backendUrl = "https://backend.stealthdrop.xyz/";
// http://localhost:3001/"; // http://45.76.66.251/

const exampleProof = [
[0, 1],
[
[2, 3],
[4, 5],
],
[6, 7],
[8, 9, 10, 11],
];

async function postData(url = "", data = {}) {
// Default options are marked with *
const response = await fetch(url, {
Expand All @@ -52,6 +43,8 @@ export default function Withdraw({
logoutOfWeb3Modal,
mainnetProvider,
provider,
transactor,
gasPrice
}) {
const [signature, setSignature] = useState();
const [proof, setProof] = useState();
Expand All @@ -61,8 +54,6 @@ export default function Withdraw({
const ensName = useLookupAddress(mainnetProvider, address);
const contracts = useContractLoader(provider);

const tx = Transactor(provider, null);

const displayAddress = address => {
if (!address) {
return "";
Expand Down Expand Up @@ -148,21 +139,32 @@ export default function Withdraw({
if (!proof) {
return;
}
const contract = contracts ? contracts["ZKT"] : "";
const contract = contracts ? contracts["SDT"] : "";
if (!contract) {
console.log("contract not found");
return;
}
const pi_a = proof["pi_a"];
const pi_b = proof["pi_b"];
const pi_c = proof["pi_c"];
const inputs = proof["inputs"];
console.log("claim: ", proof, contract);
const claimTokens = contract.connect(signer)["claimTokens"];
const returned = await tx(claimTokens(pi_a, pi_b, pi_c, inputs));
const returned = await transactor(claimTokens(proof["pi_a"], proof["pi_b"], proof["pi_c"], proof["inputs"]));
console.log("returned", returned);
};

const chestQuota = useMemo(async () => {
const contract = contracts ? contracts["SDT"] : "";
if (!proof || !contract || !gasPrice) {
console.log("proof/contract not found");
return false;
}
const quota = await contract.connect(signer)["getChestQuota"]();
const claimTokensGas = await contract.estimateGas.claimTokens(proof["pi_a"], proof["pi_b"], proof["pi_c"], proof["inputs"]);
console.log("gasPrice", gasPrice);
console.log("claimTokensGas", claimTokensGas);
console.log("estimatedGasPrice", claimTokensGas * gasPrice);
console.log("quota", quota);
return claimTokensGas * gasPrice * 5 < quota;
}, [contracts, proof, gasPrice]);

return (
<div style={{ margin: "auto", width: "70vw", display: "flex", flexDirection: "column", padding: "16px" }}>
{/* {address && (
Expand Down Expand Up @@ -194,7 +196,7 @@ export default function Withdraw({
<Tekst>
{eligibility
? "You're eligible for the airdrop!"
: "Connect a wallet eligible for the airdrop. Only MetaMask is supported as of now."}
: "Connect a wallet eligible for the airdrop."}
</Tekst>
<Bootoon key="loginbutton" shape="round" size="large" onClick={loadWeb3Modal} disabled={!!address}>
{web3Modal && web3Modal.cachedProvider && address
Expand Down Expand Up @@ -233,7 +235,7 @@ export default function Withdraw({
You are now connected to a different wallet. The tokens will be withdrawn to this anonymous wallet.
</Tekst>
)}
{!address && <Tekst>Not connected to any wallet. Switch your account through your wallet.</Tekst>}
{!address && <Tekst>Not connected to any wallet. Select a wallet to proceed.</Tekst>}
</Collapse>
</Box>

Expand All @@ -255,9 +257,17 @@ export default function Withdraw({
<p style={{ marginBottom: "0px" }}>5. Claim</p>
</Heading>
<Collapse collapsed={step != 5}>
<Tekst>
Claim tokens by submitting a transaction containing the ZK proof to the ERC-20 contract on-chain.
</Tekst>
{chestQuota ? (
<Tekst>
Claim tokens by submitting a transaction containing the ZK proof to the ERC-20 contract on-chain.
The contract treasury chest currently has enough xDai to support gasless transactions. Obtain some funds from a faucet and set your gas fees to minimal possible.
</Tekst>
) : (
<Tekst>
Claim tokens by submitting a transaction containing the ZK proof to the ERC-20 contract on-chain.
The treasury chest currently does <textit>not</textit> have enough xDai to support gasless transactions, pay your own gas.
</Tekst>
)}
<Bootoon onClick={claim}>CLAIM TOKEN</Bootoon>
</Collapse>
</Box>
Expand Down
2 changes: 1 addition & 1 deletion zkaffold-eth/packages/react-app/src/contracts/contracts.js
Original file line number Diff line number Diff line change
@@ -1 +1 @@
module.exports = ["ZKT"];
module.exports = ["SDT"];

1 comment on commit 26ddc41

@vercel
Copy link

@vercel vercel bot commented on 26ddc41 Jan 27, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.