Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .nvmrc

This file was deleted.

2 changes: 1 addition & 1 deletion hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'hardhat-deploy'
import 'hardhat-gas-reporter'

import { config } from 'dotenv'
import { BigNumber as BN, ethers } from 'ethers'
import { ethers } from 'ethers'
import fs from 'fs'
import { HardhatUserConfig } from 'hardhat/config'
import {
Expand Down
5 changes: 1 addition & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@
"git:future": "./scripts/git/future.sh",
"git:portal": "./scripts/git/portal.sh"
},
"engines": {
"node": "14"
},
"dependencies": {
"@chainlink/contracts": "^0.1.6",
"@ensdomains/ens-contracts": "^0.0.5",
Expand Down Expand Up @@ -66,7 +63,7 @@
"fs-extra": "^10.0.0",
"hardhat": "^2.6.0",
"hardhat-contract-sizer": "^2.0.2",
"hardhat-deploy": "git://github.com/teller-protocol/hardhat-deploy#84efdfa22e5c252c2415e9a0fb587f5c92993abe",
"hardhat-deploy": "^0.8.11",
"hardhat-gas-reporter": "^1.0.4",
"hardhat-shorthand": "^1.0.0",
"husky": "^4.2.3",
Expand Down
89 changes: 77 additions & 12 deletions tasks/nft/claim-nft.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BigNumberish } from 'ethers'
import { BigNumber, BigNumberish } from 'ethers'
import fs from 'fs'
import { task } from 'hardhat/config'
import { HardhatRuntimeEnvironment } from 'hardhat/types'
Expand All @@ -11,6 +11,7 @@ interface ClaimNFTArgs {
account: string
merkleIndex?: number
sendTx?: boolean
printArgs?: boolean
}

export const claimNFT = async (
Expand All @@ -19,13 +20,25 @@ export const claimNFT = async (
): Promise<void> => {
const { contracts, network, ethers, toBN, log } = hre

if (!['localhost', 'hardhat'].includes(network.name) && !args.sendTx) {
console.log()
console.log('================================================')
console.log(' Must pass --send-tx flag to execute tx')
console.log('================================================')
console.log()
return
// Validate that exactly one of printArgs or sendTx is set for non-local networks
if (!['localhost', 'hardhat'].includes(network.name)) {
if (!args.sendTx && !args.printArgs) {
console.log()
console.log('================================================')
console.log(' Must pass either --send-tx or --print-args')
console.log('================================================')
console.log()
return
}
if (args.sendTx && args.printArgs) {
console.log()
console.log('================================================')
console.log(' Cannot use both --send-tx and --print-args')
console.log(' Please choose only one')
console.log('================================================')
console.log()
return
}
}

const { account, merkleIndex } = args
Expand Down Expand Up @@ -99,9 +112,57 @@ export const claimNFT = async (
log('')

if (requests.length > 0) {
await nftDistributor
.claim(args.account, requests)
.then(({ wait }) => wait())
if (args.printArgs) {
// Print non-encoded arguments
log('Function Arguments:', { indent: 2, star: true })
log(`Contract: ${nftDistributor.address}`, { indent: 4 })
log(`Function: claim(address,tuple[])`, { indent: 4 })
log(
`Etherscan: https://etherscan.io/address/${nftDistributor.address}#writeProxyContract#F1`,
{ indent: 4 }
)
log(`Account: ${args.account}`, { indent: 4 })
log('Requests:', { indent: 4 })
requests.forEach((req, idx) => {
log(`Request ${idx}:`, { indent: 6 })
log(` merkleIndex: ${BigNumber.from(req.merkleIndex).toString()}`, {
indent: 6,
})
log(` nodeIndex: ${BigNumber.from(req.nodeIndex).toString()}`, {
indent: 6,
})
log(` amount: ${BigNumber.from(req.amount).toString()}`, { indent: 6 })
log(` merkleProof: [${req.merkleProof.join(', ')}]`, { indent: 6 })
})
log('')

// Print encoded requests for Etherscan tuple[] input
const encodedRequests = requests.map((req) => [
BigNumber.from(req.merkleIndex).toString(),
BigNumber.from(req.nodeIndex).toString(),
BigNumber.from(req.amount).toString(),
req.merkleProof,
])
log('Encoded Requests (for Etherscan tuple[] input):', {
indent: 2,
star: true,
})
log(JSON.stringify(encodedRequests), { indent: 4 })
log('')

// Print encoded calldata
const encodedData = nftDistributor.interface.encodeFunctionData('claim', [
args.account,
requests,
])
log('Full Encoded Calldata:', { indent: 2, star: true })
log(encodedData, { indent: 4 })
log('')
} else {
await nftDistributor
.claim(args.account, requests)
.then(({ wait }) => wait())
}
}

log('Done.')
Expand All @@ -113,5 +174,9 @@ task('claim-nft', 'Claims an NFT on behalf of an account')
'merkleIndex',
'Only claim tokens using the specified merkle index.'
)
.addFlag('sendTx', 'Required flag to ensure this is not ran on accident')
.addFlag('sendTx', 'Execute the transaction on-chain')
.addFlag(
'printArgs',
'Print encoded and non-encoded arguments without sending'
)
.setAction(claimNFT)
64 changes: 49 additions & 15 deletions tasks/nft/view-nfts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ export const viewNFTs = async (
claimable: {},
claimed: {},
}

// First pass: collect all claims that need to be checked
interface ClaimToCheck {
distributionIndex: number
claimIndex: number
amount: string
tierIndex: number
}
const claimsToCheck: ClaimToCheck[] = []

for (let i = 0; i < distributions.length; i++) {
const { merkleRoot, tierIndex, tokenTotal, claims } = distributions[i]
const info: MerkleDistributorInfo = {
Expand All @@ -69,21 +79,45 @@ export const viewNFTs = async (
const claimEntries = Object.entries(info.claims)
for (const [, claim] of claimEntries) {
if (!claim) continue
const isClaimed = await nftDistributor.isClaimed(i, claim.index)

const skip = (claimed && !isClaimed) || (claimable && isClaimed)
if (skip) continue

const { tierIndex } = merkleTrees[i]
if (isClaimed) {
tierIndices.claimed.add(tierIndex)
tierTokens.claimed[tierIndex] =
parseInt(claim.amount, 16) + (tierTokens.claimed[tierIndex] ?? 0)
} else {
tierIndices.claimable.add(tierIndex)
tierTokens.claimable[tierIndex] =
parseInt(claim.amount, 16) + (tierTokens.claimable[tierIndex] ?? 0)
}
claimsToCheck.push({
distributionIndex: i,
claimIndex: claim.index,
amount: claim.amount,
tierIndex: merkleTrees[i].tierIndex,
})
}
}

// Second pass: batch all isClaimed calls and execute in parallel (10 at a time)
const BATCH_SIZE = 5
const isClaimedResults: boolean[] = []

for (let i = 0; i < claimsToCheck.length; i += BATCH_SIZE) {
const batch = claimsToCheck.slice(i, i + BATCH_SIZE)
const batchPromises = batch.map((claim) =>
nftDistributor.isClaimed(claim.distributionIndex, claim.claimIndex)
)
const batchResults = await Promise.all(batchPromises)
isClaimedResults.push(...batchResults)
}

// Third pass: process results
for (let i = 0; i < claimsToCheck.length; i++) {
const claim = claimsToCheck[i]
const isClaimed = isClaimedResults[i]

const skip = (claimed && !isClaimed) || (claimable && isClaimed)
if (skip) continue

if (isClaimed) {
tierIndices.claimed.add(claim.tierIndex)
tierTokens.claimed[claim.tierIndex] =
parseInt(claim.amount, 16) + (tierTokens.claimed[claim.tierIndex] ?? 0)
} else {
tierIndices.claimable.add(claim.tierIndex)
tierTokens.claimable[claim.tierIndex] =
parseInt(claim.amount, 16) +
(tierTokens.claimable[claim.tierIndex] ?? 0)
}
}

Expand Down
Loading
Loading