├── README.md ├── Warning! ├── dump-opensea.py ├── license.txt ├── requirements.txt └── snapshots └── dos-punks-owners.json /README.md: -------------------------------------------------------------------------------- 1 | # Dump-OpenSea 2 |
3 | ██████████ █████ █████ ██████ ██████ ███████████ 4 | ░░███░░░░███ ░░███ ░░███ ░░██████ ██████ ░░███░░░░░███ 5 | ░███ ░░███ ░███ ░███ ░███░█████░███ ░███ ░███ 6 | ░███ ░███ ░███ ░███ ░███░░███ ░███ ░██████████ 7 | ░███ ░███ ░███ ░███ ░███ ░░░ ░███ ░███░░░░░░ 8 | ░███ ███ ░███ ░███ ░███ ░███ ░███ 9 | ██████████ ░░████████ █████ █████ █████ 10 | ░░░░░░░░░░ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ 11 | 12 | 13 | 14 | ███████ █████████ 15 | ███░░░░░███ ███░░░░░███ 16 | ███ ░░███ ████████ ██████ ████████ ░███ ░░░ ██████ ██████ 17 | ░███ ░███░░███░░███ ███░░███░░███░░███ ░░█████████ ███░░███ ░░░░░███ 18 | ░███ ░███ ░███ ░███░███████ ░███ ░███ ░░░░░░░░███░███████ ███████ 19 | ░░███ ███ ░███ ░███░███░░░ ░███ ░███ ███ ░███░███░░░ ███░░███ 20 | ░░░███████░ ░███████ ░░██████ ████ █████░░█████████ ░░██████ ░░████████ 21 | ░░░░░░░ ░███░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░ ░░░░░░░░ 22 | ░███ 23 | █████ 24 | ░░░░░ - BROUGHT TO YOU BY DOS PUNKS DAO 25 |26 | 27 | A simple Python script to **retrieve Owners/Holders of a whole NFT collection on ETH (OpenSea)**. 28 | 29 | Coded by [GBE](https://github.com/gbe3hunna/) for the [DOS PUNKS DAO](https://github.com/DOSPunksDAO) to thank [@maxcapacity](https://twitter.com/maxcapacity) and [@greencrosslive](https://twitter.com/greencrosslive) for all their effort in buidling such a strong #DOSLIFE. 30 | 31 | This script can be useful for any NFT project staff to Snapshot the actual holders 32 | 33 | _ 34 | 35 | Developed by DOS Punks DAO for the NFT Community. 36 | 37 | - 38 | 39 | This software is distributed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0) 40 | 41 | - 42 | 43 | 44 | # Installation 45 | 46 | #### 1) ```git clone https://github.com/DOSPunksDAO/dump-opensea.git``` 47 | 48 | #### 2) ```cd dump-opensea``` 49 | 50 | #### 3) ```pip install -r requirements.txt -U``` 51 | 52 | # Other requirements 53 | 54 | - OpenSea Collection Path/Slug 55 | - `Example: https://opensea.io/collection/dos-punks` --> `dos-punks` 56 | - Contract Address 57 | - `Example: https://opensea.io/collection/dos-punks` --> `0x495f947276749ce646f68ac8c248420045cb7b5e` 58 | - Total Minted / Total Collection Tokens 59 | - `Example: https://opensea.io/collection/dos-punks` --> `497` 60 | - Moralis Web3 API Key (FREE) 61 | - https://admin.moralis.io/web3Api 62 | 63 | 64 | # Usage 65 | 66 | #### Running with user input 67 | ``` 68 | python dump-opensea.py 69 | ``` 70 | - Fill the inputs with your data 71 | - Filter is `OPTIONAL` (Example: 2 If you want to filter by holders with 2 or more tokens...) 72 | 73 | 74 | #### Running with flags 75 | ``` 76 | -s, --slug / Slug 77 | -c, --contract / Contract Address 78 | -m, --minted / Total Minted - Total Collection Tokens 79 | -k, --apikey / Moralis Web3 API Key 80 | -f, --filter (OPTIONAL) / Filter by Tokens (2 If you want to filter by holders with 2 or more tokens...) 81 | ``` 82 | - Type ```python dump-opensea.py -h``` to get the available flags 83 | 84 | # Results 85 | 86 | - It will generate a JSON file inside `./snapshots` directory. 87 | - Will follow the next Schema: 88 | - Wallet Address: Number of tokens 89 | 90 | -------------------------------------------------------------------------------- /Warning!: -------------------------------------------------------------------------------- 1 | Somtimes this script will produce a result that is different than what is seen on the OpenSea main collection page. 2 | If the collection is on the OpenSea Shared Storefront (OPENSTORE) contract (0x495f947276749Ce646f68AC8c248420045cb7b5e), then this script will not include as part of the result the owners of NFTs for which the owner is the NFT minter. In other words, if the NFT was minted on OpenSea and is still owned by the minter, then this will not be reflected in the total NFTs owned after running the script (neither will the owner for such an NFT). 3 | Unlike above, NFTs on their own contract will be included in the script results even if the minter of the NFT is still the owner. 4 | If the OpenSea collection is on its own contract, the script can sometimes show a different number for total NFTs minted than what is displayed on OpenSea if the NFTs are held by the Null Address: 0x0000000000000000000000000000000000000000. 5 | Please reach out to bodaciouspirate on Twitter if you have questions or want to see an example. 6 | -------------------------------------------------------------------------------- /dump-opensea.py: -------------------------------------------------------------------------------- 1 | # Import necessary libraries 2 | import requests, argparse, json, sys, time 3 | import math 4 | from colorama import Fore, Back, Style, init as coloramaInit 5 | from alive_progress import alive_bar as progressBar 6 | 7 | # Initialize main vars 8 | offset = 0 9 | num = 0 10 | totalOwned = 0 11 | totalNfts = 0 12 | owners = {} 13 | filteredOwners = {} 14 | 15 | # Header 16 | def showHeader(): 17 | print(f'''{Fore.LIGHTBLUE_EX} 18 | ██████████ █████ █████ ██████ ██████ ███████████ 19 | ░░███░░░░███ ░░███ ░░███ ░░██████ ██████ ░░███░░░░░███ 20 | ░███ ░░███ ░███ ░███ ░███░█████░███ ░███ ░███ 21 | ░███ ░███ ░███ ░███ ░███░░███ ░███ ░██████████ 22 | ░███ ░███ ░███ ░███ ░███ ░░░ ░███ ░███░░░░░░ 23 | ░███ ███ ░███ ░███ ░███ ░███ ░███ 24 | ██████████ ░░████████ █████ █████ █████ 25 | ░░░░░░░░░░ ░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ 26 | 27 | 28 | 29 | ███████ █████████ 30 | ███░░░░░███ ███░░░░░███ 31 | ███ ░░███ ████████ ██████ ████████ ░███ ░░░ ██████ ██████ 32 | ░███ ░███░░███░░███ ███░░███░░███░░███ ░░█████████ ███░░███ ░░░░░███ 33 | ░███ ░███ ░███ ░███░███████ ░███ ░███ ░░░░░░░░███░███████ ███████ 34 | ░░███ ███ ░███ ░███░███░░░ ░███ ░███ ███ ░███░███░░░ ███░░███ 35 | ░░░███████░ ░███████ ░░██████ ████ █████░░█████████ ░░██████ ░░████████ 36 | ░░░░░░░ ░███░░░ ░░░░░░ ░░░░ ░░░░░ ░░░░░░░░░ ░░░░░░ ░░░░░░░░ 37 | ░███ 38 | █████ 39 | ░░░░░ - BROUGHT TO YOU BY DOS PUNKS DAO 40 | 41 | {Fore.WHITE}Created by: {Fore.LIGHTRED_EX}GBE#0001{Fore.WHITE} for DOS PUNKS DAO 42 | ''') 43 | 44 | # Init with flags 45 | def flagInit(): 46 | # Parse Flags 47 | parser = argparse.ArgumentParser() 48 | parser.add_argument("-s", "--slug", help="Slug Name") 49 | parser.add_argument("-c", "--contract", help="Contract Address") 50 | parser.add_argument("-m", "--minted", help="Total Minted") 51 | parser.add_argument("-k", "--apikey", help="API Key") 52 | parser.add_argument("-f", "--filter", help="Filter by Tokens (2 If you want to filter by holders with 2 or more tokens)") 53 | args = parser.parse_args() 54 | 55 | # Check if flags exists 56 | slug = args.slug 57 | contract = args.contract 58 | totalMinted = int(args.minted) 59 | apiKey = args.apikey 60 | tokenFilter = args.filter 61 | print("[] Slug: {}{}{}\n[] Contract Address: {}{}{}\n[] Total Minted: {}{}{}\n[] API Key: {}{}{} \n[] Filter: {}{}{}\n".format( 62 | Fore.YELLOW, args.slug, Fore.WHITE, 63 | Fore.YELLOW, args.contract, Fore.WHITE, 64 | Fore.YELLOW, args.minted, Fore.WHITE, 65 | Fore.YELLOW, args.apikey, Fore.WHITE, 66 | Fore.YELLOW, args.filter, Fore.WHITE) 67 | ) 68 | return slug, contract, totalMinted, apiKey, tokenFilter 69 | 70 | # Init with user input 71 | def inputInit(): 72 | print(f"{Fore.YELLOW}[] Project Slug: {Style.RESET_ALL}", end="") 73 | slug = input().strip() 74 | print(f"{Fore.YELLOW}[] Contract Address: {Style.RESET_ALL}", end="") 75 | contract = input().strip() 76 | print(f"{Fore.YELLOW}[] Collection Total Tokens: {Style.RESET_ALL}", end="") 77 | totalMinted = input().strip() 78 | print(f"{Fore.YELLOW}[] Moralis API Key: {Style.RESET_ALL}", end="") 79 | apiKey = input().strip() 80 | print(f"{Fore.YELLOW}[] Filter owners ? (Y/N): {Style.RESET_ALL}", end="") 81 | tokenResponse = input().strip() 82 | if ((tokenResponse.lower()) == "y"): 83 | print(f"{Fore.YELLOW}[] Minimum number of tokens holding: {Style.RESET_ALL}", end="") 84 | tokenFilter = input().strip() 85 | else: tokenFilter = None 86 | return slug, contract, totalMinted, apiKey, tokenFilter 87 | 88 | # Fatal Error --> Exit 89 | def fatalError(): 90 | print(f"\n{Fore.RED}An error has occurred. Please try again.\n") 91 | sys.exit() 92 | 93 | # Retrieving owners from API's 94 | def getOwners(slug, contract, pagination, tokenFilter, apiKey): 95 | # Iterate trough the pagination 96 | print(f'''\n{Fore.YELLOW} 97 | +------------------------------------------------------------+ 98 | | CHECKING HOLDERS. PLEASE WAIT... | 99 | +------------------------------------------------------------+{Fore.WHITE}\n''' 100 | ) 101 | try: 102 | with progressBar(int(pagination), bar='filling') as bar: 103 | for i in range(0,int(pagination)): 104 | offset = (i * 50) 105 | response = requests.get(f"https://api.opensea.io/api/v1/assets?order_direction=desc&offset={offset}&limit=50&collection={slug}").json() 106 | for asset in response['assets']: 107 | global totalNfts, totalOwned, owners, num 108 | totalNfts += 1 109 | try: web3response = requests.get(f"https://deep-index.moralis.io/api/v2/nft/{contract}/{asset['token_id']}/owners?chain=eth&format=decimal", headers={'X-API-Key': apiKey}).json() 110 | except: web3response = requests.get(f"https://deep-index.moralis.io/api/v2/nft/{contract}/{asset['token_id']}/owners?chain=eth&format=decimal", headers={'X-API-Key': apiKey}).json() 111 | totalOwned += web3response['total'] 112 | for r in web3response['result']: 113 | if r['owner_of'] in owners: owners[r['owner_of']] += 1 114 | else: owners[r['owner_of']] = 1 115 | num += 1 116 | bar() 117 | if (tokenFilter not in (None, 0)): 118 | print(f'\n\n{Fore.WHITE}[+] Filtered by holders with: {Fore.GREEN}{tokenFilter} or + Tokens{Fore.WHITE}') 119 | for _o in owners: 120 | if (int(owners[_o]) >= int(tokenFilter)): filteredOwners[_o] = owners[_o] 121 | owners = filteredOwners 122 | except: fatalError() 123 | 124 | # Export to JSON 125 | def exportJSON(slug): 126 | global totalNfts, totalOwned, owners 127 | outputFile = open(f'./snapshots/{slug}-owners.json', 'w') 128 | json.dump(owners, outputFile) 129 | time.sleep(1.5) 130 | print(f'''\n{Fore.YELLOW} 131 | +------------------------------------------------------------+ 132 | | SNAPSHOT HAS BEEN TAKEN | 133 | +------------------------------------------------------------+\n 134 | {Fore.GREEN} [+] Total NFTS: {Fore.WHITE}{totalNfts}{Fore.YELLOW} 135 | {Fore.GREEN} [+] Total Owned: {Fore.WHITE}{totalOwned}{Fore.YELLOW} 136 | {Fore.GREEN} [+] Total Owners: {Fore.WHITE}{len(owners)}{Fore.YELLOW} 137 | {Fore.GREEN} [+] Exported File: {Fore.WHITE}./snapshots/{slug}-owners.json{Fore.YELLOW}\n\n''' 138 | ) 139 | 140 | # Main handle 141 | def main(): 142 | coloramaInit() 143 | showHeader() 144 | try: slug, contract, totalMinted, apiKey, tokenFilter = flagInit() #Init with flags 145 | except: slug, contract, totalMinted, apiKey, tokenFilter = inputInit() #Init with user input 146 | pagination = int(math.ceil((int(totalMinted)) / 50)) #Get pagination 147 | getOwners(slug, contract, pagination, tokenFilter, apiKey) #Main method 148 | exportJSON(slug) #Export results 149 | 150 | #Start 151 | main() -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | ██████████ ███████ █████████ 2 | ░░███░░░░███ ███░░░░░███ ███░░░░░███ 3 | ░███ ░░███ ███ ░░███░███ ░░░ 4 | ░███ ░███░███ ░███░░█████████ 5 | ░███ ░███░███ ░███ ░░░░░░░░███ 6 | ░███ ███ ░░███ ███ ███ ░███ 7 | ██████████ ░░░███████░ ░░█████████ 8 | ░░░░░░░░░░ ░░░░░░░ ░░░░░░░░░ 9 | 10 | 11 | 12 | ███████████ █████ 13 | ░░███░░░░░███ ░░███ 14 | ░███ ░███ █████ ████ ████████ ░███ █████ █████ 15 | ░██████████ ░░███ ░███ ░░███░░███ ░███░░███ ███░░ 16 | ░███░░░░░░ ░███ ░███ ░███ ░███ ░██████░ ░░█████ 17 | ░███ ░███ ░███ ░███ ░███ ░███░░███ ░░░░███ 18 | █████ ░░████████ ████ █████ ████ █████ ██████ 19 | ░░░░░ ░░░░░░░░ ░░░░ ░░░░░ ░░░░ ░░░░░ ░░░░░░ 20 | 21 | 22 | 23 | ██████████ █████████ ███████ 24 | ░░███░░░░███ ███░░░░░███ ███░░░░░███ 25 | ░███ ░░███ ░███ ░███ ███ ░░███ 26 | ░███ ░███ ░███████████ ░███ ░███ 27 | ░███ ░███ ░███░░░░░███ ░███ ░███ 28 | ░███ ███ ░███ ░███ ░░███ ███ 29 | ██████████ █████ █████ ░░░███████░ 30 | ░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░░░ 31 | 32 | 33 | _ 34 | 35 | Developed by DOS Punks DAO for the NFT Community. 36 | 37 | - 38 | 39 | This software is distributed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License (CC BY-NC-SA 4.0) 40 | 41 | - 42 | 43 | You are free to: 44 | Share — copy and redistribute the material in any medium or format 45 | Adapt — remix, transform, and build upon the material 46 | The licensor cannot revoke these freedoms as long as you follow the license terms. 47 | 48 | Under the following terms: 49 | 50 | Attribution — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use. 51 | 52 | NonCommercial — You may not use the material for commercial purposes. 53 | 54 | ShareAlike — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original. 55 | 56 | No additional restrictions — You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits. 57 | 58 | Notices: 59 | 60 | You do not have to comply with the license for elements of the material in the public domain or where your use is permitted by an applicable exception or limitation. 61 | No warranties are given. The license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. 62 | 63 | - 64 | 65 | C:\DOSPunksDAO.exe -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Production 2 | requests >= 2.25.1 3 | alive_progress >= 2.1.0 4 | colorama >= 0.4.4 5 | argparse >= 1.4.0 -------------------------------------------------------------------------------- /snapshots/dos-punks-owners.json: -------------------------------------------------------------------------------- 1 | {} --------------------------------------------------------------------------------