import { ethers } from 'ethers'; import MerkleTree from 'merkletreejs'; class PudgyPenguinsClaimer { constructor(contractAddress, abi, provider) { this.contract = new ethers.Contract(contractAddress, abi, provider); this.batchData = new Map(); } async loadBatchData(batchId) { if (!this.batchData.has(batchId)) { const response = await fetch(`/api/batches/${batchId}`); const batch = await response.json(); this.batchData.set(batchId, batch); } return this.batchData.get(batchId); } async claimPenguin(tokenId, signer) { const batchId = Math.floor(tokenId / 500); const batch = await this.loadBatchData(batchId); const penguin = batch.penguins.find(p => p.tokenId === tokenId); if (!penguin) throw new Error('Penguin not found in batch data'); const contractWithSigner = this.contract.connect(signer); const tx = await contractWithSigner.claimPenguin( tokenId, penguin.traits.background, penguin.traits.body, penguin.traits.face, penguin.traits.head, penguin.proof ); return await tx.wait(); } async verifyPenguin(tokenId, owner) { const batchId = Math.floor(tokenId / 500); const batch = await this.loadBatchData(batchId); const penguin = batch.penguins.find(p => p.tokenId === tokenId); if (!penguin) return false; // Verify off-chain const leaves = batch.penguins.map(p => keccak256( ethers.utils.defaultAbiCoder.encode( ['uint256', 'string', 'string', 'string', 'string', 'address'], [p.tokenId, p.traits.background, p.traits.body, p.traits.face, p.traits.head, owner] ) ) ); const tree = new MerkleTree(leaves, keccak256, { sort: true }); const leaf = keccak256( ethers.utils.defaultAbiCoder.encode( ['uint256', 'string', 'string', 'string', 'string', 'address'], [tokenId, penguin.traits.background, penguin.traits.body, penguin.traits.face, penguin.traits.head, owner] ) ); return tree.verify(penguin.proof, leaf, tree.getRoot()); } async getPenguinTraits(tokenId) { const batchId = Math.floor(tokenId / 500); const batch = await this.loadBatchData(batchId); const penguin = batch.penguins.find(p => p.tokenId === tokenId); return penguin ? penguin.traits : null; } }