Getting all balances with some token held. Useful for airdrops.
This guide will require you to understand some basic scripting through JSON files to calculate the logic you want for tokens in the format you wish. If you need a website version of this, you can use https://juno.tools/airdrops/
Native (JUNO) Export
To airdrop, you will need a state export file. These come from a full node that is synced up to the network and stopped. These nodes typically only hold 30 days' worth of data, so ensure you take an export between now and 30 days ago. You can ensure your height is within this time by checking Mintscan.
# stop the juno node from running thencd~/.juno/junodexportHEIGHT-HERE>juno_state_export.json# Where HEIGHT-HERE is the block height you want, for example 7000000
Some exports require > and others require 2>, this is due to an SDK bug that outputs it to STDERR. If you see it throwing out the text to your screen, try using the other ">" or "2>" operator. We want it to get formatted into the file.
This can take upwards of 30 minutes to complete, so be patient.
Once the command exits, check your file size. It should be large. If it only shows 4KB, then you have not exported the state correctly due to an issue with height or version. This will have been output in the file itself, so follow the error to fix the reason it could not export correctly. You can check the file size like so
du -sh juno_state_export.json
Then check to ensure the file is valid and no extra logs got into your JSON. If the below fails, there is likely text at the top of your file that you need to remove from logs
Once the above is complete, you are ready to follow the write scripts to parse, edit, and get the data into a format for your needs. You most likely need to stream the file in most languages due to the size. Here is an example in python for streaming the sections.
CW20 Balances
This section will require you to run 2 scripts which gets all balances held at a select block on chain.\
# script1.shCW20_CONTRACT="juno1hnftys64ectjfynm6qjk9my8jd3f6l9dq9utcd3dy8ehwrsx9q4q7n9uxt"LIMIT="500"REST_NODE="https://api.juno.strange.love:443"# make dir CW20s if not already mademkdir-pCW20s# the block height we want to query atHEIGHT=7131467next_pag_key=""urlencode() {# urlencode <string> old_lc_collate=$LC_COLLATE LC_COLLATE=Clocal length="${#1}"for (( i =0; i < length; i++ )); dolocal c="${1:$i:1}"case $c in[a-zA-Z0-9.~_-])printf'%s'"$c" ;;*)printf'%%%02X'"'$c" ;;esacdone LC_COLLATE=$old_lc_collate}# Loop through until we dont have any more pageswhiletrue; do# get a random UUID for this file to merge later UUID=$(uuidgen) && FILENAME="CW20s/cw20-$UUID.json"# check if next_pag_key is emptyif [[ -z $next_pag_key ]]; thenecho"no key = 1st run" curl -X GET "$REST_NODE/cosmwasm/wasm/v1/contract/$CW20_CONTRACT/state?pagination.limit=$LIMIT" -H "accept: application/json" -H "x-cosmos-block-height: $HEIGHT" > $FILENAME
elseecho"Running with page key: $next_pag_key" URL_CODE=$(urlencode $next_pag_key) curl -X GET "$REST_NODE/cosmwasm/wasm/v1/contract/$CW20_CONTRACT/state?pagination.key=$URL_CODE&pagination.limit=$LIMIT" -H "accept: application/json" -H "x-cosmos-block-height: $HEIGHT" > $FILENAME
fi# echo "Getting all keys from $FILENAME" next_pag_key=`jq-r '.pagination.next_key' $FILENAME`# if next_pag_key if null, then we are doneif [[ $next_pag_key =="null" ]]; thenecho"No more pages, Finished!"breakfidoneecho"Please run script2.py now to convert the CW20s folder files -> balances.json"
# script2.pyimport base64import jsonimport oscurrent_dir = os.path.dirname(os.path.realpath(__file__))CW20s = os.path.join(current_dir, "CW20s")defhex_string_to_uft8(hex_string):returnbytearray.fromhex(hex_string).decode()defbase64_to_uft8(base64_string):return base64.b64decode(base64_string).decode()balances ={}total_balances =0for file in os.listdir(CW20s):# read the filewithopen(os.path.join(CW20s, file), "r")as f: data = json.load(f)# get models key if foundif"models"notin data:continue modules =list(data["models"])for m in modules: key =hex_string_to_uft8(m["key"])if"balance"notin key:break address =str(# you may have to edit the replace statement depending on the contract key.replace("balance", "").replace("\u0000\u0007", "") )# balancejuno1000xz25ydz8h9rwgnv30l9p0x500dvj0s9yyc9 -> juno1000xz25ydz8h9rwgnv30l9p0x500dvj0s9yyc9 balance =int(base64_to_uft8(m["value"]).replace('"', "") )if balance <=0:continue balances[address]= balance total_balances += balance# save balances to a file as JSONwithopen(os.path.join(current_dir, "balances.json"), "w")as f: balances ={ k: v for k, v insorted(balances.items(), key=lambdax: x[1], reverse=True)} json.dump(balances, f, indent=2)print("Balances saved to balances.json. Total Value: {total_balances:.0f}")