Skip to content

Commit

Permalink
Merge pull request #17 from fakenickels/feat/special-kittens-distributor
Browse files Browse the repository at this point in the history
  • Loading branch information
fakenickels committed Dec 6, 2021
2 parents 6db5394 + 716b4f7 commit 1304687
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 17 deletions.
3 changes: 2 additions & 1 deletion .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ NEXT_PUBLIC_CONTRACT_ADDRESS="0xfD211f3B016a75bC8d73550aC5AdC2f1cAE780C0"
NEXT_PUBLIC_MASTER_KITTEN_CONTRACT_ADDRESS="0x88361C5cEc6524B1D46172a0c40B55B632961c61"
NEXT_PUBLIC_LP_PAIR_CONTRACT_ADDRESS="0xec5Aec9Eb2fadC4d09475B20F2eF34E399df3d17"
NEXT_PUBLIC_KITTENS_MERKLE_DISTRIBUTOR_ADDRESS=0xbde784aca1B3D4edcBd1FC2C408fD48f2307416B
NEXT_PUBLIC_KITTENS_HD_MINTER_ADDRESS=0xE5E47661bE771a36d22442091d5429Ec7773D79E
NEXT_PUBLIC_KITTENS_HD_MINTER_ADDRESS=0xE5E47661bE771a36d22442091d5429Ec7773D79E
NEXT_PUBLIC_SPECIAL_DISTRIBUTOR_ADDRESS=0xfcfBAA46f449e3804c8203D707C0a5CDeB5D215c
42 changes: 33 additions & 9 deletions contracts/SpecialKittensDistributor.sol
Original file line number Diff line number Diff line change
@@ -1,42 +1,66 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "./HonoraryKittens.sol";
import "./KittensHD.sol";

contract SpecialKittensDistributor is Ownable {
contract SpecialKittensDistributor is Ownable, IERC721Receiver {
HonoraryKittens honoraryKittens;

KittensHD kittensHD;

mapping(address => bool) public claims;
mapping(uint256 => bool) public claimBitmap;

constructor(address kittensHDAddress, address honoraryKittensAddress) {
kittensHD = KittensHD(kittensHDAddress);
honoraryKittens = HonoraryKittens(honoraryKittensAddress);
}

function claim() public {
require(honoraryKittens.balanceOf(msg.sender) > 0);
require(!claims[msg.sender], "You've already claimed");
require(
honoraryKittens.balanceOf(msg.sender) > 0,
"You don't have any honorary kittens"
);

// check if the sender already minted a kitten hd due to the KittensHD contract bug
uint256 index = 0;
while (true) {

// for of kittenHD.balance of msg.sender
for (uint256 index = 0; index < kittensHD.balanceOf(msg.sender); index++) {
uint256 tokenId_ = kittensHD.tokenOfOwnerByIndex(msg.sender, index);
require(
// check if the honorary holder has already minted within the reserved range
tokenId_ < 667 && tokenId_ > 687,
tokenId_ < 667 || tokenId_ > 687,
"You've already claimed your free HD"
);
index++;
}

claims[msg.sender] = true;
// check if honorary kitten was already "spent"
for (
uint256 index = 0;
index < honoraryKittens.balanceOf(msg.sender);
index++
) {
uint256 tokenId_ = honoraryKittens.tokenOfOwnerByIndex(msg.sender, index);
require(!claimBitmap[tokenId_], "You've already claimed this token");
claimBitmap[tokenId_] = true;
}

// call daoClaim from KittensHD and transfer to msg.sender
uint256 tokenId = kittensHD.getGeneralMintCounter();

kittensHD.unpauseMinting();
kittensHD.daoAnyClaim(1);
kittensHD.safeTransferFrom(address(this), msg.sender, tokenId);
kittensHD.pauseMinting();
}

function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external override returns (bytes4) {
return this.onERC721Received.selector;
}
}
24 changes: 21 additions & 3 deletions pages/kittens-hd.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { utils } from "ethers";
import { useWallet } from "use-wallet";
import { useKittenHDMinterMethods } from "../src/utils/useKittensHdMinter";
import { useRKittenClaim } from "../src/utils/useRKittenClaim";
import { useSpecialClaim } from "../src/utils/useSpecialKittensDistributor";

const metamaskChangeToFantom = () => {
return window.ethereum.request({
Expand Down Expand Up @@ -124,6 +125,7 @@ export default function KittensHD() {
const kittensHD = useKittenHDMethods();
const kittensMinter = useKittenHDMinterMethods();
const rkittenClaim = useRKittenClaim();
const specialClaim = useSpecialClaim();
const wallet = useWallet();

const leftKittens = 10_000 - (kittensHD.generalClaimedCount || 0);
Expand Down Expand Up @@ -278,15 +280,31 @@ export default function KittensHD() {
})
.catch((e) => {
toast.dismiss();
toast.error(`Error minting ${quantity} kittens: ${e.message}`);
toast.error(`${e.data?.message || e.message}`);
});
}}
>
Claim free HD for OG Kittens
</Button>
<div className="w-8/12 flex items-center flex-col">
<Button disabled>Claim for special kittens</Button>
<p>Claim for special kittens will be resumed soon</p>
<Button
onClick={() => {
specialClaim
.claimKittens()
.then((txn) => {
toast.success(
`Successfully claimed 1 Kitten HD. Check below.`
);
})
.catch((e) => {
toast.dismiss();
console.log(e);
toast.error(`${e.data?.message || e.message}`);
});
}}
>
Claim for special kittens
</Button>
</div>
<div className="w-8/12 flex items-center flex-col">
<Button
Expand Down
21 changes: 17 additions & 4 deletions scripts/deploy_special_kitten_distributor.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,33 @@ async function main() {
const Contract = await hre.ethers.getContractFactory(
"SpecialKittensDistributor"
);
const contract = await Contract.deploy(
const kittensHDAddress = "0xad956DF38D04A9A555E079Cf5f3fA59CB0a25DC9";
const args = [
// KittensHD
"0xad956DF38D04A9A555E079Cf5f3fA59CB0a25DC9",
kittensHDAddress,
// Kittens Specials
"0xE65469083B4f50d1EcD089584c671Bb1d23F9AC7"
);
"0xE65469083B4f50d1EcD089584c671Bb1d23F9AC7",
];
const contract = await Contract.deploy(...args);
await contract.deployed();

console.log("Deployed to:", contract.address);

// get KittensHD contract and call grantDAOMemberRole for contract.address
const kittensHDContract = await ethers.getContractAt(
"KittensHD",
kittensHDAddress,
deployer
);

console.log("Granting role...");
await kittensHDContract.grantDAOMemberRole(contract.address);

if (process.env.CHAIN_SCAN_TOKEN) {
console.log("Verifying ze contract");
await hre.run("verify:verify", {
address: contract.address,
constructorArguments: args,
});
}
}
Expand Down
36 changes: 36 additions & 0 deletions src/utils/useSpecialKittensDistributor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from "react";
import * as ethers from "ethers";
import Contract from "../../artifacts/contracts/SpecialKittensDistributor.sol/SpecialKittensDistributor.json";

export const useWeb3 = () => {
const provider: React.MutableRefObject<
ethers.providers.Web3Provider | undefined
> = React.useRef();
const contract: React.MutableRefObject<any | undefined> = React.useRef();

React.useEffect(() => {
provider.current = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.current.getSigner();
contract.current = new ethers.Contract(
process.env.NEXT_PUBLIC_SPECIAL_DISTRIBUTOR_ADDRESS as string,
Contract.abi as any,
signer
);
});

return [provider, contract];
};

export const useSpecialClaim = () => {
const [, contract] = useWeb3();

const claimKittens = async () => {
return contract.current.claim().then((res: any) => {
return res.wait();
});
};

return {
claimKittens,
};
};
106 changes: 106 additions & 0 deletions test/special_kittens_distributor.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
const chai = require("chai");
const expect = chai.expect;
const { solidity } = require("ethereum-waffle");
const merkleRoot = require("../airdrop/kittens-hd-rkitten-airdrop/merkle-root.json");

chai.use(solidity);

describe("KittensHD minter", function () {
it("should deploy and be able to claim", async function () {
this.timeout(50000000000000000000000000000000000000);
const [user] = await ethers.getSigners();
const deployer = "0x03D7D343bB9008d182FDB0B9100903f0952bf358";
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [deployer],
});

const signer = await ethers.getSigner(deployer);

const Contract = await ethers.getContractFactory(
"SpecialKittensDistributor"
);

const kittensHDAddress = "0xad956DF38D04A9A555E079Cf5f3fA59CB0a25DC9";
const honoraryKittensAddress = "0xE65469083B4f50d1EcD089584c671Bb1d23F9AC7";

console.log("Deploying contract...");
const contract = await Contract.deploy(
kittensHDAddress,
honoraryKittensAddress
);

console.log("Initializing kittens hd");
const kittensHDContract = await ethers.getContractAt(
"KittensHD",
kittensHDAddress,
signer
);

// setup merkle distributor
console.log("Granting role...");
await kittensHDContract.grantDAOMemberRole(contract.address);

console.log("Pausing minting...");
await kittensHDContract.pauseMinting();

const honoraryHolderAddress = "0x0fdBAeCF59193e8Ff96d9b392B8C24E892AE2b26";
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [honoraryHolderAddress],
});

await network.provider.send("hardhat_setBalance", [
honoraryHolderAddress,
"0xffffffffffffffff",
]);

const honoraryHolder = await ethers.getSigner(honoraryHolderAddress);

const receipt = await contract
.connect(honoraryHolder)
.claim()
.then((receipt) => receipt.hash)
.catch((e) => e.message);

expect(receipt).to.not.contain(
`VM Exception while processing transaction: reverted with reason string`
);
expect(receipt).to.not.contain(`Transaction reverted:`);

const balance = await kittensHDContract.balanceOf(honoraryHolderAddress);

expect(balance).to.equal(1);

const shouldFailReceipt = await contract
.connect(honoraryHolder)
.claim()
.then((receipt) => receipt.hash)
.catch((e) => e.message);
// should not be able to claim twice for a honorary kitten ticket
expect(shouldFailReceipt).to.contain(`You've already claimed this token`);
// not honorary holder = fail
expect(
await contract
.connect(user)
.claim()
.then((receipt) => receipt.hash)
.catch((e) => e.message)
).to.contain(`You don't have any honorary kittens`);

const incidentMinterAddress = "0x9945daF5dCd39C9A3556d27c16af343765e5630C";
await hre.network.provider.request({
method: "hardhat_impersonateAccount",
params: [incidentMinterAddress],
});

const incidentMinter = await ethers.getSigner(incidentMinterAddress);
expect(
await contract
.connect(incidentMinter)
.claim()
.then((receipt) => receipt.hash)
.catch((e) => e.message)
).to.contain(`You've already claimed your free HD`);
});
});

1 comment on commit 1304687

@vercel
Copy link

@vercel vercel bot commented on 1304687 Dec 6, 2021

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.