├── .gitignore ├── 00_Config.R ├── 01_Operations_Data.R ├── 02_IS_Generation.R ├── 03_BS_Generation.R ├── 04_Tax_Generation.R ├── LICENSE.md ├── README.md ├── forms ├── 2021 │ ├── f1040s1.pdf │ ├── f1040sd.pdf │ └── f8949.pdf ├── 2022 │ ├── f1040s1.pdf │ ├── f1040sd.pdf │ └── f8949.pdf ├── 2023 │ ├── f1040s1.pdf │ ├── f1040sd.pdf │ └── f8949.pdf └── 2024 │ ├── f1040s1.pdf │ ├── f1040sd.pdf │ └── f8949.pdf ├── functions ├── cb_import.R ├── classify_tx.R ├── list_check.R ├── quick_case.R └── tzkt_api.R └── tez-tax.Rproj /.gitignore: -------------------------------------------------------------------------------- 1 | *.RData 2 | *.Rhistory 3 | *.csv 4 | data/* 5 | .Rproj.user 6 | 00_Config - Actual.R 7 | 02a_IS_Adjustments.R 8 | 03a_8949_Adjustment.R 9 | -------------------------------------------------------------------------------- /00_Config.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | # Import packages 20 | library("tidyverse") 21 | library("jsonlite") 22 | library("lubridate") 23 | library("magrittr") 24 | library("readr") 25 | library("staplr") # Staplr requires R 4.1.2 or lower 26 | 27 | # Load functions 28 | source("functions/list_check.R") 29 | source("functions/quick_case.R") 30 | source("functions/tzkt_api.R") 31 | 32 | # Adjust numeric display 33 | options(scipen=999) 34 | 35 | # Create data folder, if applicable 36 | dir.create("data", showWarnings=FALSE) 37 | 38 | # Define wallet addresses 39 | wallets <- c( 40 | "tz1a2ZeWmyNQ8BiuFNTE4vmFEP9MBaP76QPX", # datcsv 41 | "tz1L5vaycmTzEDekjDJSFZJ1V8FPwAUCVSDM" # datcsv1 42 | ) 43 | 44 | # Define currency 45 | currency <- "usd" 46 | 47 | # Define date span [min, max] 48 | date_span <- c("2024-01-01T00:00:00Z", "2024-12-31T23:59:59Z") 49 | 50 | # Path to Coinbase transaction data, if applicable (Otherwise set to NA) 51 | cb_path <- NA 52 | 53 | # Legal name (to be used on tax documents) 54 | legal_name <- "datcsv" 55 | 56 | # Social security # (to be used on tax documents) 57 | ssn <- "000-00-0000" 58 | -------------------------------------------------------------------------------- /01_Operations_Data.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | # Get account operations: First pass 20 | limit_ops <- 1000 21 | date_span_all <- c("2016-01-01T00:00:00Z", date_span[2]) 22 | for (i in 1:length(wallets)) { 23 | operations_i <- tzkt_operations( 24 | address=wallets[i], limit=limit_ops, span=date_span_all, quote=currency 25 | ) 26 | while ((nrow(operations_i) > 0) & (nrow(operations_i) %% limit_ops) == 0) { 27 | level <- min(operations_i$level + 1) 28 | operations_i %<>% bind_rows(., 29 | tzkt_operations( 30 | address=wallets[i], level=level, limit=limit_ops, span=date_span_all, 31 | quote=currency 32 | ) 33 | ) 34 | } 35 | if (i == 1) operations <- operations_i 36 | else operations %<>% bind_rows(., operations_i) 37 | } 38 | 39 | # Drop potential excess variables 40 | op_names <- c( 41 | "type", "id", "level", "timestamp", "block", "hash", "counter", "sender", 42 | "gasLimit", "gasUsed", "storageLimit", "storageUsed", "bakerFee", 43 | "storageFee", "allocationFee", "target", "amount", "parameter", "status", 44 | "hasInternals", "quote", "initiator", "nonce", "errors", "contractBalance", 45 | "originatedContract" 46 | ) 47 | operations %<>% select(., any_of(op_names)) 48 | 49 | # Get account operations: Second pass (Get operations by hash) 50 | operations_hash <- operations %>% 51 | filter(., target[[2]] %in% wallets | target[[1]] %in% wallets) %>% 52 | distinct(., hash) 53 | 54 | for (i in 1:nrow(operations_hash)) { 55 | operations_i <- tzkt_operations_hash(operations_hash[i, ], quote=currency) 56 | operations_i %<>% select(., any_of(op_names)) 57 | if ("parameter" %in% names(operations_i)) { 58 | if ("value" %in% names(operations_i$parameter)) { 59 | if (class(operations_i$parameter$value) != "list") { 60 | operations_i$parameter$value <- list(operations_i$parameter$value) 61 | } 62 | } 63 | } 64 | operations %<>% bind_rows(., operations_i) 65 | } 66 | 67 | # Get account operations: OBJKT v1 contract (Early bigmap workaround) 68 | contracts <- c("KT1Dno3sQZwR5wUCWxzaohwuJwG3gX1VWj1Z") 69 | for (i in 1:length(contracts)) { 70 | operations_i <- tzkt_operations( 71 | address=contracts[i], limit=limit_ops, span=date_span_all, quote=currency 72 | ) 73 | while (nrow(operations_i) > 0 & (nrow(operations_i) %% limit_ops) == 0) { 74 | level <- min(operations_i$level + 1) 75 | operations_i %<>% bind_rows(., 76 | tzkt_operations(address=contracts[i], level=level, limit=limit_ops) 77 | ) 78 | } 79 | if (i == 1) objkt_operations <- operations_i 80 | else objkt_operations %<>% bind_rows(., operations_i) 81 | } 82 | objkt_operations_hash <- objkt_operations %>% distinct(., hash) 83 | for (i in 1:nrow(objkt_operations_hash)) { 84 | operations_i <- filter(objkt_operations, hash == objkt_operations_hash[i, ]) 85 | parameter_entry <- operations_i$parameter$entrypoint 86 | parameter_value <- operations_i$parameter$value 87 | token_receiver <- list_check(operations_i$parameter$value, "to_") 88 | if (("swap" %in% parameter_entry > 0) & (token_receiver %in% wallets)) { 89 | operations_i %<>% select(., any_of(op_names)) 90 | operations %<>% bind_rows(., operations_i) 91 | } 92 | } 93 | 94 | # Combine and clean operations data 95 | operations %<>% 96 | arrange(., id, hash) %>% 97 | distinct(.) 98 | 99 | # Save transaction data 100 | save(operations, file="data/operations.RData") 101 | -------------------------------------------------------------------------------- /02_IS_Generation.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | # Load operations data 20 | load(file="data/operations.RData") 21 | 22 | # Split nested features in operations data 23 | operations$initiatorAlias <- operations$initiator$alias 24 | operations$initiatorAddress <- operations$initiator$address 25 | operations$senderAlias <- operations$sender$alias 26 | operations$SenderAddress <- operations$sender$address 27 | operations$targetAlias <- operations$target$alias 28 | operations$targetAddress <- operations$target$address 29 | operations$parameterEntry <- operations$parameter$entrypoint 30 | operations$parameterValue <- operations$parameter$value 31 | operations$quote <- operations$quote[[1]] 32 | 33 | op_hash <- "0" 34 | j <- 0 35 | batch_list <- c( 36 | "collect", "harvest", "fulfill_ask", "retract_offer", "retract_ask", "ask", 37 | "retract_bid", "fulfill_bid", "fulfill_offer", "listing_accept" 38 | ) 39 | for (i in 1:nrow(operations)) { 40 | 41 | # Adjust batch transactions 42 | if (i == 1) { 43 | if (sum(operations$parameterEntry[i] %in% batch_list) > 0) { 44 | op_hash <- operations$hash[i] 45 | } 46 | } 47 | else if ( 48 | ( 49 | (sum(operations$parameterEntry[i] %in% batch_list) > 0) | 50 | (operations$hash[i] == op_hash) 51 | ) & 52 | ( 53 | (operations$hash[i] != operations$hash[i-1]) | 54 | (sum(operations$parameterEntry[i-1] %in% c("update_operators")) > 0) 55 | ) 56 | ) { 57 | if (sum(operations$parameterEntry[i] %in% batch_list) > 0) { 58 | op_hash <- operations$hash[i] 59 | j <- j + 1 60 | } 61 | else { 62 | op_hash <- operations$hash[i] 63 | } 64 | operations$hash[i] <- paste0(op_hash, "_", j) 65 | } 66 | } 67 | 68 | # Clean operations data 69 | operations %<>% 70 | group_by(., hash) %>% 71 | mutate(., 72 | sumBakerFee = sum(bakerFee, na.rm=TRUE), 73 | sumStorageFee = sum(storageFee, na.rm=TRUE), 74 | sumAllocationFee = sum(allocationFee, na.rm=TRUE) 75 | ) %>% 76 | ungroup(.) 77 | 78 | operations %<>% 79 | filter(., (type == "transaction") | (SenderAddress %in% wallets)) %>% 80 | distinct(., id, hash, .keep_all=TRUE) %>% 81 | mutate(., 82 | xtzAmount = ifelse( 83 | (status != "backtracked") & (status != "failed") & 84 | (!is.na(amount)) & (type == "transaction"), 85 | amount / 1000000, 0 86 | ), 87 | xtzFee = ifelse( 88 | (status != "backtracked") & (status != "failed"), 89 | (sumBakerFee + sumStorageFee + sumAllocationFee) / 1000000, 90 | (sumBakerFee) / 1000000 91 | ), 92 | xtzSent = ifelse(SenderAddress %in% wallets, xtzAmount + xtzFee, 0), 93 | xtzReceived = ifelse(targetAddress %in% wallets, xtzAmount, 0), 94 | parameterValue = ifelse(parameterValue == "NULL", NA, parameterValue), 95 | tokenID = "", 96 | tokenAmount = 0, 97 | tokenSender = "", 98 | tokenReceiver = "", 99 | tokenSent = 0, 100 | tokenReceived = 0, 101 | walletTx = TRUE, 102 | xtzBuy = FALSE, 103 | xtzProceeds = 0, # Proceeds on xtz sent 104 | xtzGainLoss = 0, # Gain (loss) on xtz sent 105 | tokenProceeds = 0, # Proceeds on token sent 106 | tokenGainLoss = 0, # Gain (loss) on token sent 107 | costBasis = NA # Cost basis of all xtz/tokens received 108 | ) %>% 109 | select(., any_of(c( 110 | "id", "level", "timestamp", "hash", "type", "status", "quote", 111 | "initiatorAddress", "SenderAddress", "targetAddress", "parameterEntry", 112 | "parameterValue", "xtzAmount", "xtzFee", "xtzSent", "xtzReceived", 113 | "tokenID", "tokenAmount", "tokenSender", "tokenReceiver", "tokenSent", 114 | "tokenReceived", "walletTx", "xtzBuy", "xtzProceeds", "xtzGainLoss", 115 | "tokenProceeds", "tokenGainLoss", "costBasis" 116 | ))) 117 | 118 | # Generate income statement from operations data: 119 | source("functions/classify_tx.R") 120 | 121 | # Adjust data 122 | is %<>% 123 | mutate(., 124 | timestamp = as_datetime(timestamp), 125 | tokenSent = ifelse(tokenSender %in% wallets, tokenAmount, 0), 126 | tokenReceived = ifelse(tokenReceiver %in% wallets, tokenAmount, 0) 127 | ) %>% 128 | select(., -xtzAmount, -tokenAmount) %>% 129 | arrange(., timestamp) 130 | 131 | # Add exchange data: 132 | save(is, file="data/is_exchange.RData") 133 | load("data/is_exchange.RData") 134 | if (!is.na(cb_path)) source("functions/cb_import.R") 135 | 136 | # Save income statement data 137 | save(is, file="data/is.RData") 138 | save(is, file="data/is_original.RData") 139 | -------------------------------------------------------------------------------- /03_BS_Generation.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | # Load income statement data 20 | load(file="data/is.RData") 21 | 22 | # Adjust tokenID for easier debugging 23 | is %<>% mutate(., tokenID = str_replace(tokenID, "_", "/")) 24 | 25 | # Generate empy balance sheet dataset 26 | bs <- tibble( 27 | timestamp = POSIXct(), 28 | asset = character(), 29 | quantity = double(), 30 | costBasis = double() 31 | ) 32 | 33 | # Generate empty tax form 8949 dataset 34 | tax_8949 <- tibble( 35 | Description = character(), 36 | Date_Acquired = Date(), 37 | Date_Sold = Date(), 38 | Proceeds = double(), 39 | Cost_Basis = double(), 40 | Codes = character(), 41 | Adjustment = double(), 42 | Gain_Loss = double() 43 | ) 44 | 45 | # Initialize variables 46 | xtz_income <- 0 47 | xtz_income_data <- is[0, ] 48 | 49 | # Loop through rows of income statement to generate tax figures 50 | for (i in 1:nrow(is)) { 51 | 52 | # Initialize variables 53 | xtz_balance <- 0 54 | xtz_cost <- 0 55 | xtz_proceeds <- 0 56 | token_balance <- 0 57 | token_cost <- 0 58 | token_proceeds <- 0 59 | subtract_j <- 0 60 | 61 | # Isolate row 62 | is_i <- is[i, ] 63 | 64 | ############################################################################## 65 | # Adjust Tezos sent/received: # 66 | # (1) If Tezos is sent and received in the same transaction, net the values.# 67 | # (2) For token transfers, base the calculation on sender/receiver. # 68 | # (3) For all other transactions, base the calculation on highest value. # 69 | # (*) This assumption should be used for estimates only and is not # 70 | # an accurate or long-term solution. # 71 | ############################################################################## 72 | 73 | # Adjust XTZ sent/received 74 | if (is_i$xtzSent > 0 & is_i$xtzReceived > 0) { 75 | if (is_i$xtzReceived >= is_i$xtzSent) { 76 | is_i$xtzReceived <- is_i$xtzReceived - is_i$xtzSent 77 | is_i$xtzSent <- 0 78 | } 79 | else { 80 | is_i$xtzSent <- is_i$xtzSent - is_i$xtzReceived 81 | is_i$xtzReceived <- 0 82 | } 83 | } 84 | 85 | ############################################################################## 86 | # Do not calculate gain (loss) for purchases or income: # 87 | # (1) If 'xtzBuy' is TRUE, mark as purchase. # 88 | # (2) If Tezos are received but no tokens are sent, mark as income. # 89 | ############################################################################## 90 | 91 | # Add Tezos purchases to balance sheet 92 | if (is_i$xtzBuy) { 93 | bs %<>% 94 | add_row(., 95 | timestamp = is_i$timestamp, 96 | asset = "xtz", 97 | quantity = is_i$xtzReceived, 98 | costBasis = ifelse( 99 | is.na(is_i$costBasis), is_i$quote, is_i$costBasis / is_i$xtzReceived 100 | ) 101 | ) 102 | next 103 | } 104 | 105 | # Add Tezos income to balance sheet 106 | else if ((is_i$xtzReceived > 0) & (is_i$tokenSent == 0)) { 107 | bs %<>% 108 | add_row(., 109 | timestamp = is_i$timestamp, 110 | asset = "xtz", 111 | quantity = is_i$xtzReceived, 112 | costBasis = is_i$quote 113 | ) 114 | xtz_income <- xtz_income + is_i$quote * is_i$xtzReceived 115 | xtz_income_data %<>% bind_rows(., is_i) 116 | next 117 | } 118 | 119 | ############################################################################## 120 | # Calculate gain (loss) on Tezos sent. # 121 | ############################################################################## 122 | 123 | if (is_i$xtzSent > 0) { 124 | 125 | # Initialize variables 126 | xtz_balance <- is_i$xtzSent 127 | xtz_cost <- 0 128 | 129 | if (is_i$xtzProceeds == 0) { 130 | xtz_proceeds <- is_i$quote * is_i$xtzSent 131 | } else { 132 | xtz_proceeds <- is_i$xtzProceeds 133 | } 134 | 135 | # Subtract Tezos from balance sheet, calculate tax figures 136 | for (j in 1:nrow(bs)) { 137 | if (xtz_balance <= 0) break 138 | if (bs$asset[j] == "xtz" & bs$quantity[j] > 0) { 139 | 140 | # Find how much can be reduced from the balance sheet 141 | subtract_j <- min(bs$quantity[j], xtz_balance) 142 | 143 | # Reduce the balance sheet accordingly 144 | bs$quantity[j] <- bs$quantity[j] - subtract_j 145 | 146 | # Calculate remaining transaction balance 147 | xtz_balance <- xtz_balance - subtract_j 148 | 149 | # Calculate transaction cost basis 150 | xtz_cost <- xtz_cost + subtract_j * bs$costBasis[j] 151 | 152 | # Update tax form 8949 dataset (Transfers adjusted in 'classify_tx.R') 153 | tax_8949 %<>% 154 | add_row(., 155 | Description = paste(subtract_j, bs$asset[j]), 156 | Date_Acquired = as_date(bs$timestamp[j]), 157 | Date_Sold = as_date(is_i$timestamp), 158 | Proceeds = round(subtract_j * is_i$quote, 2), 159 | Cost_Basis = round(subtract_j * bs$costBasis[j], 2), 160 | Codes = NA, 161 | Adjustment = NA, 162 | Gain_Loss = Proceeds - Cost_Basis 163 | ) 164 | } 165 | } 166 | 167 | # If balance sheet deficit, issue warning and assume cost basis of zero 168 | if (xtz_balance > 0) { 169 | 170 | warning(cat("\nTezos deficit, cost basis assumed zero:", i, is_i$id)) 171 | tax_8949 %<>% 172 | add_row(., 173 | Description = paste(xtz_balance, bs$asset[j]), 174 | Date_Acquired = NA, 175 | Date_Sold = as_date(is_i$timestamp), 176 | Proceeds = round(xtz_balance * (xtz_proceeds / is_i$xtzSent), 2), 177 | Cost_Basis = 0, 178 | Codes = NA, 179 | Adjustment = NA, 180 | Gain_Loss = Proceeds 181 | ) 182 | 183 | # Add row to balance sheet for debugging 184 | bs %<>% 185 | add_row(., 186 | timestamp = is_i$timestamp, 187 | asset = "xtz", 188 | quantity = -1 * xtz_balance, 189 | costBasis = NA 190 | ) 191 | } 192 | 193 | # Log proceeds and gain (loss) to income statement 194 | is$xtzProceeds[i] <- round(xtz_proceeds, 2) 195 | is$xtzGainLoss[i] <- round(xtz_proceeds, 2) - round(xtz_cost, 2) 196 | } 197 | 198 | ############################################################################## 199 | # Calculate gain (loss) on tokens sent. # 200 | ############################################################################## 201 | 202 | if (is_i$tokenSent > 0) { 203 | 204 | # Initialize variables 205 | token_balance <- is_i$tokenSent 206 | token_cost <- 0 207 | 208 | if (is_i$tokenProceeds == 0) token_proceeds <- is_i$xtzReceived * is_i$quote 209 | else token_proceeds <- is_i$tokenProceeds 210 | 211 | j <- 1 212 | 213 | # Subtract tokens from balance sheet, calculate tax figures 214 | while (j <= nrow(bs)) { 215 | if (token_balance <= 0) break 216 | if (bs$asset[j] == is_i$tokenID & bs$quantity[j] > 0) { 217 | 218 | # Find how much can be reduced from the balance sheet 219 | subtract_j <- min(bs$quantity[j], token_balance) 220 | 221 | # Reduce the balance sheet accordingly 222 | bs$quantity[j] <- bs$quantity[j] - subtract_j 223 | 224 | # Calculate remaining transaction balance 225 | token_balance <- token_balance - subtract_j 226 | 227 | # Calculate transaction cost basis 228 | token_cost <- token_cost + subtract_j * bs$costBasis[j] 229 | 230 | # Update tax form 8949 dataset, ignore transfers 231 | if (!(is_i$case %in% c("Token transfer", "Wallet transfer"))) { 232 | tax_8949 %<>% 233 | add_row(., 234 | Description = paste(subtract_j, bs$asset[j]), 235 | Date_Acquired = as_date(bs$timestamp[j]), 236 | Date_Sold = as_date(is_i$timestamp), 237 | Proceeds = ( 238 | round(subtract_j * (token_proceeds / is_i$tokenSent), 2) 239 | ), 240 | Cost_Basis = round(subtract_j * bs$costBasis[j], 2), 241 | Codes = NA, 242 | Adjustment = NA, 243 | Gain_Loss = Proceeds - Cost_Basis 244 | ) 245 | } 246 | } 247 | 248 | j <- j + 1 249 | 250 | } 251 | 252 | # If balance sheet deficit, issue warning and assume cost basis of zero 253 | if (token_balance > 0) { 254 | 255 | ########################################################################## 256 | # Token deficit assumption (In progress, not working): # 257 | # (1) If there is no record of a token entering the balance sheet, # 258 | # the token is assumed to have a cost basis of zero. # 259 | # (2) For tax form 8949, an acquisition date is required; in order to # 260 | # impute this value, the timestamp value is set to the last time # 261 | # the wallet had a positive token balance update. # 262 | # (*) This assumption should be used for estimates only and is not # 263 | # an accurate or long-term solution. # 264 | ########################################################################## 265 | 266 | warning(cat("\nToken deficit assumption:", is_i$id, is_i$tokenID)) 267 | def_acq <- NA 268 | 269 | if (!(is_i$case %in% c("Token transfer", "Wallet transfer"))){ 270 | tax_8949 %<>% 271 | add_row(., 272 | Description = paste(token_balance, is_i$tokenID), 273 | Date_Acquired = def_acq, 274 | Date_Sold = as_date(is_i$timestamp), 275 | Proceeds = round(token_balance * (token_proceeds / is_i$tokenSent), 2), 276 | Cost_Basis = 0, 277 | Codes = NA, 278 | Adjustment = NA, 279 | Gain_Loss = Proceeds 280 | ) 281 | } 282 | 283 | # Add row to balance sheet for debugging 284 | bs %<>% 285 | add_row(., 286 | timestamp = is_i$timestamp, 287 | asset = is_i$tokenID, 288 | quantity = -1 * token_balance, 289 | costBasis = NA 290 | ) 291 | } 292 | 293 | # Log proceeds and gain (loss) to income statement 294 | is$tokenProceeds[i] <- round(token_proceeds, 2) 295 | is$tokenGainLoss[i] <- round(token_proceeds, 2) - round(token_cost, 2) 296 | 297 | # Add Tezos received in token transactions to balance sheet 298 | if (is_i$xtzReceived > 0) { 299 | bs %<>% 300 | add_row(., 301 | timestamp = is_i$timestamp, 302 | asset = "xtz", 303 | quantity = is_i$xtzReceived, 304 | costBasis = is_i$quote 305 | ) 306 | } 307 | } 308 | 309 | ############################################################################## 310 | # Log any tokens received to balance sheet. # 311 | ############################################################################## 312 | 313 | # Calculate total transaction proceeds (as cost basis) 314 | if (is.na(is_i$costBasis)) { 315 | is$costBasis[i] <- xtz_proceeds + token_proceeds 316 | } 317 | 318 | # Add tokens received to balance sheet 319 | if (!is.na(is_i$tokenReceived)) { 320 | if (is_i$tokenReceived > 0) { 321 | bs %<>% 322 | add_row(., 323 | timestamp = is_i$timestamp, 324 | asset = is_i$tokenID, 325 | quantity = is_i$tokenReceived, 326 | costBasis = is$costBasis[i] / is_i$tokenReceived 327 | ) 328 | } 329 | } 330 | 331 | if (is.na(is_i$tokenReceived)) { 332 | print(is_i$id) 333 | } 334 | 335 | # If token sent and token received in same transaction, issue warning 336 | if ((is_i$tokenSent > 0) & (is_i$tokenReceived > 0)) { 337 | warning(cat("\nToken sent and received in same transaction!", is_i$id)) 338 | } 339 | 340 | # Balance reconciliation (For debugging) 341 | # if (i %% 50 == 0) { 342 | # is$balTZ[i] <- tzkt_balance(wallets[1], is_i$level) 343 | # is$balBS[i] <- sum(select(filter(bs, asset == "xtz"), "quantity")) 344 | # } 345 | } 346 | 347 | # Add token ID column to tax data 348 | tax_8949 %<>% 349 | mutate(., 350 | Token_Quantity = str_split(Description, " ", simplify=TRUE)[, 1], 351 | Token_ID = str_split(Description, " ", simplify=TRUE)[, 2], 352 | Token_ID_Short = ifelse( 353 | nchar(Token_ID) > 16, 354 | substr(Token_ID, nchar(Token_ID) - 15, nchar(Token_ID)), 355 | Token_ID 356 | ), 357 | Description = paste(Token_Quantity, Token_ID_Short) 358 | ) 359 | 360 | # Save income statement, balance sheet, and tax data 361 | save(is, file="data/is_updated.RData") 362 | save(bs, file="data/bs.RData") 363 | save(tax_8949, file="data/tax_8949.RData") 364 | save(tax_8949, file="data/tax_8949_original.RData") 365 | save(xtz_income_data, file="data/xtz_income_data.RData") 366 | -------------------------------------------------------------------------------- /04_Tax_Generation.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | ################################################################################ 20 | # Please note that all trades of tokens with missing acquisition dates are 21 | # assumed to be short-term trades.Tokens are typically missing an acquisition 22 | # date when they were received as the result of an airdrop or direct transfer. 23 | ################################################################################ 24 | 25 | # Load income statement, balance sheet, and tax data 26 | load(file="data/is_updated.RData") 27 | load(file="data/bs.RData") 28 | load(file="data/tax_8949.RData") 29 | load(file="data/xtz_income_data.RData") 30 | 31 | # Export personal income data 32 | write_csv(xtz_income_data, "data/xtz_income.csv") 33 | 34 | # Create pdf directories 35 | tax_8949_dir <- "data/8949" 36 | dir.create(tax_8949_dir, showWarnings=FALSE) 37 | 38 | pdf_f1040sd_dir <- "data/f1040sd" 39 | dir.create(pdf_f1040sd_dir, showWarnings=FALSE) 40 | 41 | pdf_f1040s1_dir <- "data/f1040s1" 42 | dir.create(pdf_f1040s1_dir, showWarnings=FALSE) 43 | 44 | # Remove zero proceed sales 45 | # tax_8949 %<>% filter(., round(Proceeds, 2) > 0.00) 46 | 47 | # Filter tax year, add short-term/long-term data 48 | tax_8949 %<>% 49 | filter(., Date_Sold >= date_span[1], Date_Sold <= date_span[2]) %>% 50 | mutate(., Short_Term = replace_na(as.numeric(Date_Sold - Date_Acquired) <= 365, TRUE)) 51 | 52 | xtz_income_data %<>% filter(., timestamp >= date_span[1], timestamp <= date_span[2]) 53 | 54 | # Format tax form 8949 data for export 55 | tax_8949_intuit <- tax_8949 %>% 56 | select(., 57 | `Asset Amount` = Token_Quantity, 58 | `Asset Name` = Token_ID_Short, 59 | `Received Date` = Date_Acquired, 60 | `Date Sold` = Date_Sold, 61 | `Proceeds (USD)` = Proceeds, 62 | `Cost Basis (USD)` = Cost_Basis, 63 | `Gain (USD)` = Gain_Loss 64 | ) %>% 65 | mutate(., 66 | `Asset Amount` = sprintf("%.8f", as.numeric(`Asset Amount`)), 67 | `Asset Name` = toupper(str_replace(`Asset Name`, "/", "_")), 68 | `Received Date` = format(`Received Date`, "%m/%d/%Y"), 69 | `Date Sold` = format(`Date Sold`, "%m/%d/%Y"), 70 | `Proceeds (USD)` = sprintf("%.8f", `Proceeds (USD)`), 71 | `Cost Basis (USD)` = sprintf("%.8f", `Cost Basis (USD)`), 72 | `Gain (USD)` = sprintf("%.8f", `Gain (USD)`), 73 | `Type` = "Short Term" 74 | ) 75 | 76 | # Adjust date format for 8949 report 77 | tax_8949 %<>% mutate(., 78 | Date_Acquired = format(Date_Acquired, "%m/%d/%Y"), 79 | Date_Sold = format(Date_Sold, "%m/%d/%Y") 80 | ) 81 | 82 | # Write tax form 8949 data to CSV file 83 | tax_8949_intuit %>% write_csv(., file=paste0(tax_8949_dir, "/tax_8949_full.csv")) 84 | 85 | # Write tax form 8949 to turbotax compatible CSV files 86 | for (i in 1:ceiling(nrow(tax_8949) / 3999)) { 87 | tax_8949_intuit %>% 88 | filter(., row_number() %in% (1 + (i - 1) * 3999):(i * 3999)) %>% 89 | write_csv(., file=paste0(tax_8949_dir, "/tax_8949_", str_pad(i, 2, pad="0"), ".csv")) 90 | } 91 | 92 | # Split 8949 into short/long-term datasets 93 | tax_8949_short <- tax_8949 %>% filter(., Short_Term) 94 | tax_8949_long <- tax_8949 %>% filter(., !Short_Term) 95 | 96 | # Generate PDF files 97 | tax_year <- year(date_span[2]) 98 | if (tax_year == 2021) { 99 | f8949 <- "forms/2021/f8949.pdf" 100 | f1040sd <- "forms/2021/f1040sd.pdf" 101 | f1040s1 <- "forms/2021/f1040s1.pdf" 102 | s1_offset <- 0 103 | } else if (tax_year == 2022) { 104 | f8949 <- "forms/2022/f8949.pdf" 105 | f1040sd <- "forms/2022/f1040sd.pdf" 106 | f1040s1 <- "forms/2022/f1040s1.pdf" 107 | s1_offset <- 5 108 | } else if (tax_year == 2023) { 109 | f8949 <- "forms/2023/f8949.pdf" 110 | f1040sd <- "forms/2023/f1040sd.pdf" 111 | f1040s1 <- "forms/2023/f1040s1.pdf" 112 | s1_offset <- 5 113 | } else { 114 | f8949 <- "forms/2024/f8949.pdf" 115 | f1040sd <- "forms/2024/f1040sd.pdf" 116 | f1040s1 <- "forms/2024/f1040s1.pdf" 117 | s1_offset <- 5 118 | } 119 | 120 | # Generate tax form 1040 schedule 1 121 | xtz_income <- sum(xtz_income_data$quote * (xtz_income_data$xtzReceived - xtz_income_data$xtzSent)) 122 | if (xtz_income > 0) { 123 | 124 | # Update fields 125 | f1040s1_fields <- get_fields(input_filepath=f1040s1) 126 | f1040s1_fields[[1]][[3]] <- legal_name 127 | f1040s1_fields[[2]][[3]] <- ssn 128 | f1040s1_fields[[27+s1_offset]][[3]] <- "Miscellaneous crypto income" 129 | f1040s1_fields[[28+s1_offset]][[3]] <- "" 130 | f1040s1_fields[[29+s1_offset]][[3]] <- sprintf("%.2f", xtz_income) 131 | f1040s1_fields[[30+s1_offset]][[3]] <- sprintf("%.2f", xtz_income) 132 | f1040s1_fields[[31+s1_offset]][[3]] <- sprintf("%.2f", xtz_income) 133 | 134 | # Generate PDF file 135 | set_fields( 136 | input_filepath=f1040s1, 137 | output_filepath=paste0(pdf_f1040s1_dir, "/f1040s1.pdf"), 138 | fields=f1040s1_fields, 139 | overwrite=TRUE 140 | ) 141 | } 142 | 143 | # Generate tax form 1040 schedule D 144 | ShortTermProceeds = tax_8949 %>% filter(., Short_Term) %>% select(., Proceeds) %>% sum(.) 145 | ShortTermCosts = tax_8949 %>% filter(., Short_Term) %>% select(., Cost_Basis) %>% sum(.) 146 | ShortTermAdjustment = tax_8949 %>% filter(., Short_Term) %>% select(., Adjustment) %>% sum(., na.rm=TRUE) 147 | ShortTermGainLoss = tax_8949 %>% filter(., Short_Term) %>% select(., Gain_Loss) %>% sum(.) 148 | 149 | LongTermProceeds = tax_8949 %>% filter(., !Short_Term) %>% select(., Proceeds) %>% sum(.) 150 | LongTermCosts = tax_8949 %>% filter(., !Short_Term) %>% select(., Cost_Basis) %>% sum(.) 151 | LongTermAdjustment = tax_8949 %>% filter(., !Short_Term) %>% select(., Adjustment) %>% sum(., na.rm=TRUE) 152 | LongTermGainLoss = tax_8949 %>% filter(., !Short_Term) %>% select(., Gain_Loss) %>% sum(.) 153 | 154 | f1040sd_fields <- get_fields(input_filepath=f1040sd) 155 | f1040sd_fields[[1]][[3]] <- legal_name 156 | f1040sd_fields[[2]][[3]] <- ssn 157 | f1040sd_fields[[4]][[3]] <- "2" 158 | 159 | # Short-term gain/loss 160 | f1040sd_fields[[17]][[3]] <- sprintf("%.2f", ShortTermProceeds) 161 | f1040sd_fields[[18]][[3]] <- sprintf("%.2f", ShortTermCosts) 162 | if (ShortTermAdjustment > 0) { 163 | f1040sd_fields[[19]][[3]] <- sprintf("%.2f", ShortTermAdjustment) 164 | } 165 | f1040sd_fields[[20]][[3]] <- sprintf("%.2f", ShortTermGainLoss) 166 | f1040sd_fields[[24]][[3]] <- sprintf("%.2f", ShortTermGainLoss) 167 | 168 | # Long-term gain/loss 169 | f1040sd_fields[[37]][[3]] <- sprintf("%.2f", LongTermProceeds) 170 | f1040sd_fields[[38]][[3]] <- sprintf("%.2f", LongTermCosts) 171 | if (ShortTermAdjustment > 0) { 172 | f1040sd_fields[[39]][[3]] <- sprintf("%.2f", LongTermAdjustment) 173 | } 174 | f1040sd_fields[[40]][[3]] <- sprintf("%.2f", LongTermGainLoss) 175 | f1040sd_fields[[45]][[3]] <- sprintf("%.2f", LongTermGainLoss) 176 | 177 | # Combined figures 178 | f1040sd_fields[[46]][[3]] <- sprintf("%.2f", ShortTermGainLoss + LongTermGainLoss) 179 | f1040sd_fields[[48]][[3]] <- "2" 180 | f1040sd_fields[[55]][[3]] <- "2" 181 | 182 | # Generate PDF file 183 | set_fields( 184 | input_filepath=f1040sd, 185 | output_filepath=paste0(pdf_f1040sd_dir, "/f1040sd.pdf"), 186 | fields=f1040sd_fields, 187 | overwrite=TRUE 188 | ) 189 | 190 | # Generate tax form 8949, short-term transactions 191 | if (nrow(tax_8949_short) > 0) { 192 | for (i in seq(1, nrow(tax_8949_short), by=14)) { 193 | 194 | # Iterator for file name 195 | if (i == 1) k <- 1 196 | else k <- k + 1 197 | 198 | # Update form identification fields 199 | f8949_fields <- get_fields(input_filepath=f8949) 200 | f8949_fields[[1]][[3]] <- legal_name 201 | f8949_fields[[2]][[3]] <- ssn 202 | f8949_fields[[5]][[3]] <- "3" 203 | 204 | # Update capital gain/loss entry fields 205 | for (j in 1:min(14, nrow(tax_8949_short) + 1 - i)) { 206 | f8949_fields[[6 + (j - 1) * 8]][[3]] <- tax_8949_short[[i + j - 1, 1]] 207 | f8949_fields[[7 + (j - 1) * 8]][[3]] <- tax_8949_short[[i + j - 1, 2]] 208 | f8949_fields[[8 + (j - 1) * 8]][[3]] <- tax_8949_short[[i + j - 1, 3]] 209 | f8949_fields[[9 + (j - 1) * 8]][[3]] <- sprintf("%.2f", tax_8949_short[[i + j - 1, 4]]) 210 | f8949_fields[[10 + (j - 1) * 8]][[3]] <- sprintf("%.2f", tax_8949_short[[i + j - 1, 5]]) 211 | f8949_fields[[11 + (j - 1) * 8]][[3]] <- tax_8949_short[[i + j - 1, 6]] 212 | if (!is.na(tax_8949_short[[i + j - 1, 7]])) { 213 | f8949_fields[[12 + (j - 1) * 8]][[3]] <- sprintf("%.2f", tax_8949_short[[i + j - 1, 7]]) 214 | } 215 | f8949_fields[[13 + (j - 1) * 8]][[3]] <- sprintf("%.2f", tax_8949_short[[i + j - 1, 8]]) 216 | } 217 | 218 | # Update capital gain/loss total fields 219 | f8949_fields[[118]][[3]] <- sprintf("%.2f", sum(tax_8949_short[i:(i + 13), 4], na.rm=TRUE)) 220 | f8949_fields[[119]][[3]] <- sprintf("%.2f", sum(tax_8949_short[i:(i + 13), 5], na.rm=TRUE)) 221 | f8949_fields[[121]][[3]] <- sprintf("%.2f", sum(tax_8949_short[i:(i + 13), 7], na.rm=TRUE)) 222 | f8949_fields[[122]][[3]] <- sprintf("%.2f", sum(tax_8949_short[i:(i + 13), 8], na.rm=TRUE)) 223 | 224 | # Generate PDF file 225 | set_fields( 226 | input_filepath=f8949, 227 | output_filepath=paste0(tax_8949_dir, "/f8949_short_", str_pad(k, 4, pad="0"), ".pdf"), 228 | fields=f8949_fields, 229 | overwrite=TRUE 230 | ) 231 | } 232 | } 233 | 234 | 235 | # Generate tax form 8949, long-term transactions 236 | if (nrow(tax_8949_long) > 0) { 237 | for (i in seq(1, nrow(tax_8949_long), by=14)) { 238 | 239 | # Iterator for file name 240 | if (i == 1) k <- 1 241 | else k <- k + 1 242 | 243 | # Update form identification fields 244 | f8949_fields <- get_fields(input_filepath=f8949) 245 | f8949_fields[[122 + 1]][[3]] <- legal_name 246 | f8949_fields[[122 + 2]][[3]] <- ssn 247 | f8949_fields[[122 + 5]][[3]] <- "3" 248 | 249 | # Update capital gain/loss entry fields 250 | for (j in 1:min(14, nrow(tax_8949_long) + 1 - i)) { 251 | f8949_fields[[122 + 6 + (j - 1) * 8]][[3]] <- tax_8949_long[[i + j - 1, 1]] 252 | f8949_fields[[122 + 7 + (j - 1) * 8]][[3]] <- tax_8949_long[[i + j - 1, 2]] 253 | f8949_fields[[122 + 8 + (j - 1) * 8]][[3]] <- tax_8949_long[[i + j - 1, 3]] 254 | f8949_fields[[122 + 9 + (j - 1) * 8]][[3]] <- sprintf("%.2f", tax_8949_long[[i + j - 1, 4]]) 255 | f8949_fields[[122 + 10 + (j - 1) * 8]][[3]] <- sprintf("%.2f", tax_8949_long[[i + j - 1, 5]]) 256 | f8949_fields[[122 + 11 + (j - 1) * 8]][[3]] <- tax_8949_long[[i + j - 1, 6]] 257 | if (!is.na(tax_8949_long[[i + j - 1, 7]])) { 258 | f8949_fields[[122 + 12 + (j - 1) * 8]][[3]] <- sprintf("%.2f", tax_8949_long[[i + j - 1, 7]]) 259 | } 260 | f8949_fields[[122 + 13 + (j - 1) * 8]][[3]] <- sprintf("%.2f", tax_8949_long[[i + j - 1, 8]]) 261 | } 262 | 263 | # Update capital gain/loss total fields 264 | f8949_fields[[122 + 118]][[3]] <- sprintf("%.2f", sum(tax_8949_long[i:(i + 13), 4], na.rm=TRUE)) 265 | f8949_fields[[122 + 119]][[3]] <- sprintf("%.2f", sum(tax_8949_long[i:(i + 13), 5], na.rm=TRUE)) 266 | f8949_fields[[122 + 121]][[3]] <- sprintf("%.2f", sum(tax_8949_long[i:(i + 13), 7], na.rm=TRUE)) 267 | f8949_fields[[122 + 122]][[3]] <- sprintf("%.2f", sum(tax_8949_long[i:(i + 13), 8], na.rm=TRUE)) 268 | 269 | # Generate PDF file 270 | set_fields( 271 | input_filepath=f8949, 272 | output_filepath=paste0(tax_8949_dir, "/f8949_long_", str_pad(k, 4, pad="0"), ".pdf"), 273 | fields=f8949_fields, 274 | overwrite=TRUE 275 | ) 276 | } 277 | } 278 | 279 | # Merge PDF files (Note: staple_pdf will fail with a large number of files) 280 | staple <- staple_pdf( 281 | input_directory=tax_8949_dir, 282 | output_filepath=paste0(tax_8949_dir, "/f8949_Final.pdf"), 283 | overwrite=TRUE 284 | ) 285 | if (staple > 0) warning("Failed to merge 8949 forms.") 286 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tez-tax 2 | 3 | 'tez-tax' is comprised of a series of R code and supplementary files which may provide potentially useful information for estimating gains or losses realized through trading XTZ and associated tokens on the Tezos blockchain. The code makes use of a first in, first out (FIFO) accounting methodology for gain or loss estimates and is geared toward U.S.-based users. 'tez-tax' and any code, outputs, or information derived thereof are provided as-is and at your own risk. Only a certified tax professional can accurately assess the tax implications of trading XTZ or associated tokens on the Tezos blockchain. 4 | 5 | ## Disclaimer 6 | 7 | The authors of 'tez-tax' are not tax professionals and 'tez-tax' is not a tax solution. 'tez-tax' and any code, outputs, or information derived thereof should not be considered a substitute for legal advice, tax advice, audit advice, accounting advice, or brokerage advice under the guidance of a licensed professional. 8 | 9 | ## Issues 10 | 11 | 'tez-tax' is no longer actively maintained. As such, there are issues with the current version. Notable known issues: 12 | 13 | * No explicit logic has been written to account for staking. 14 | * Coinbase import no longer works due to changes in file structure provided by Coinbase. 15 | 16 | ## Instructions 17 | 18 | 'tez-tax' is a work in progress and a number of simplifying assumptions are made during the course of the code logic. Further, the identification and classification of smart contract operations is exceedingly complex by nature. It is important to thoroughly understand and debug each step in order to ensure any provided estimates are as accurate as possible. 'tez-tax' will never provide perfectly accurate outputs and only seeks to provide potentially useful information for estimating gain or loss estimates. It is worth noting that it is not possible to comprehensively categorize all smart contract operations and is important to validate all outputs at each step of the process. 19 | 20 | Users of 'tez-tax' should have a firm understanding of the R programming language and the tax implications of their Tezos blockchain activity. All 'tez-tax' outputs should be thoroughly reviewed for accuracy. Failure to critically validate the outputs can result in erroneous estimates. 21 | 22 | * Update the contents of configuration file, '00_Config.R', and run it. The configuration file should be run prior to each 'tez-tax' session. 23 | + Currently, 'tez-tax' only provides support for Coinbase as an exchange, alternative exchange data will need to be manually imported and added to the initial income statement, please refer to 'functions/cb_import.R' for example. 24 | + To download Coinbase exchange data, navigate to [Coinbase.com/reports](https://www.coinbase.com/reports) and generate a transaction history CSV report. 25 | + Currently 'tez-tax' does not support any exchanges other than Coinbase. Other exchange transactions will have to be added or modified manually. 26 | 27 | * Download operations data via the [TzKT API](https://api.tzkt.io/) by running '01_Operations_Data.R'. 28 | + This step will likely take the longest as it downloads blockchain data for all wallets included in the configuration file. 29 | + It is important to note that the [TzKT API](https://api.tzkt.io/) calls may not download transactions where both a wallet listed in the configuration file did not initiate a transaction and a wallet listed in the configuration file did not receive XTZ. For example, airdropped FA2 tokens will not be downloaded in this step due to this limitation. 30 | + Once the code has been run once, it does not need to be run again unless the configuration or code itself has been updated. 31 | 32 | * Classify operation groups and generate an initial income statement by running '02_IS_Generation.R'. 33 | + This step relies on 'functions/classify_tx.R' to classify transactions and calculate applicable income statement fields. 34 | + Once the code has finished running, users should verify that all transactions have been classified correctly. 35 | + Any misclassified or miscalculated rows or fields should be manually adjusted. 36 | + In the event that any operations are unclassified, nrow(filter(is, is.na(case))) > 0, the unclassified rows should be manually adjusted. 37 | 38 | * Generate necessary gain or loss data via a dynamic income statement/balance sheet relationship by running '03_BS_Generation.R'. 39 | + This step attempts to calculate gains or losses using the provided income statement and exchange data. 40 | + It is important to note that, due to the API limitations mentioned above, airdropped FA2 tokens from external accounts and similar transactions may not appear in the initial generated income statement. 41 | + A warning will appear if one of these tokens is otherwise interacted with, as such an interaction will result in a deficient token balance. 42 | + When a deficient token balance is encountered, the code will assume the token was acquired with a cost basis of 0 XTZ and no acquisition date will be provided for the token in the 'tax_8949' output. 43 | + Missing acquisition dates should be manually input by the user. 44 | + A number of additional, strong assumptions are made during this process that should be thoroughly reviewed in the code. 45 | + Once the code has finished running, the balances provided in the balance sheet data should be recoonciled to those provided via the [TzKT API](https://api.tzkt.io/) at various points in time. 46 | + Data included in the 'tax_8949' dataset are used to calculate capital gains income (i.e., Schedule D). Data included in 'xtzIncome_data' are used to calculate other income (i.e., Schedule 1). It is important to ensure that these classifications are correct. 47 | 48 | * For U.S. users, it may be useful to generate tax documents using '04_Tax_Generation.R'. **The outputs of this step should be used for informational purposes only and are provided as-is. Only a certified tax professional can accurately assess the tax implications of trading XTZ or associated tokens on the Tezos blockchain.** 49 | + This step relies on the ['staplr' R package](https://cran.r-project.org/web/packages/staplr/index.html). 50 | + Please note that the current code assumes all transactions are short-term trades for the time being. 51 | 52 | ## License 53 | 54 | Copyright 2025 datcsv 55 | 56 | Licensed under the Apache License, Version 2.0 (the "License"); 57 | you may not use this file except in compliance with the License. 58 | You may obtain a copy of the License at 59 | 60 | ```http://www.apache.org/licenses/LICENSE-2.0``` 61 | 62 | Unless required by applicable law or agreed to in writing, software 63 | distributed under the License is distributed on an "AS IS" BASIS, 64 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 65 | See the License for the specific language governing permissions and 66 | limitations under the License. 67 | -------------------------------------------------------------------------------- /forms/2021/f1040s1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2021/f1040s1.pdf -------------------------------------------------------------------------------- /forms/2021/f1040sd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2021/f1040sd.pdf -------------------------------------------------------------------------------- /forms/2021/f8949.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2021/f8949.pdf -------------------------------------------------------------------------------- /forms/2022/f1040s1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2022/f1040s1.pdf -------------------------------------------------------------------------------- /forms/2022/f1040sd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2022/f1040sd.pdf -------------------------------------------------------------------------------- /forms/2022/f8949.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2022/f8949.pdf -------------------------------------------------------------------------------- /forms/2023/f1040s1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2023/f1040s1.pdf -------------------------------------------------------------------------------- /forms/2023/f1040sd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2023/f1040sd.pdf -------------------------------------------------------------------------------- /forms/2023/f8949.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2023/f8949.pdf -------------------------------------------------------------------------------- /forms/2024/f1040s1.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2024/f1040s1.pdf -------------------------------------------------------------------------------- /forms/2024/f1040sd.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2024/f1040sd.pdf -------------------------------------------------------------------------------- /forms/2024/f8949.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/datcsv/tez-tax/0a765c2322727fc859791ae6f8ababe01b076238/forms/2024/f8949.pdf -------------------------------------------------------------------------------- /functions/cb_import.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | # Reload income statement data 20 | load(file="data/is_exchange.RData") 21 | 22 | # Import Coinbase data 23 | cb <- read_csv(file=cb_path, skip=7, show_col_types=FALSE) 24 | cb %<>% 25 | mutate(., Asset2 = substr(Notes, nchar(Notes)-2, nchar(Notes))) %>% 26 | filter(., Asset == "XTZ" | Asset2 == "XTZ") 27 | 28 | # Generate empty income statement row 29 | cb_is <- is[0, ] 30 | 31 | # For each Coinbase transaction, add row to income statment 32 | for (i in 1:nrow(cb)) { 33 | 34 | cb_i <- cb[i, ] 35 | x <- is[0, ] 36 | x[1, ] <- NA 37 | 38 | x$timestamp <- cb_i$Timestamp 39 | x$status <- "applied" 40 | x$quote <- cb_i$`Spot Price at Transaction` 41 | x$xtzBuy <- FALSE 42 | 43 | x$xtzProceeds <- 0 44 | x$xtzGainLoss <- 0 45 | x$tokenProceeds <- 0 46 | x$tokenGainLoss <- 0 47 | x$tokenSent <- 0 48 | x$tokenReceived <- 0 49 | 50 | # Coinbase buy 51 | if (cb_i$`Transaction Type` == "Buy") { 52 | x$xtzSent <- 0 53 | x$xtzReceived <- cb_i$`Quantity Transacted` 54 | x$costBasis <- cb_i$`Total (inclusive of fees and/or spread)` 55 | x$case <- "Coinbase buy" 56 | x$xtzBuy <- TRUE 57 | } 58 | 59 | # Coinbase sell 60 | else if (cb_i$`Transaction Type` == "Sell") { 61 | x$xtzSent <- cb_i$`Quantity Transacted` 62 | x$xtzReceived <- 0 63 | x$xtzProceeds <- cb_i$`Total (inclusive of fees and/or spread)` 64 | x$case <- "Coinbase sell" 65 | x$quote <- cb_i$`Total (inclusive of fees and/or spread)` / cb_i$`Quantity Transacted` 66 | } 67 | 68 | # Coinbase send 69 | else if (cb_i$`Transaction Type` == "Send") { 70 | x$xtzSent <- cb_i$`Quantity Transacted` 71 | x$xtzReceived <- 0 72 | x$case <- "Coinbase send" 73 | x$targetAddress <- substr(cb_i$Notes, nchar(cb_i$Notes)-35, nchar(cb_i$Notes)) 74 | } 75 | 76 | # Coinbase receive 77 | else if (cb_i$`Transaction Type` == "Receive") { 78 | x$xtzSent <- 0 79 | x$xtzReceived <- cb_i$`Quantity Transacted` 80 | x$case <- "Coinbase receive" 81 | } 82 | 83 | # Coinbase convert 84 | else if (cb_i$`Transaction Type` == "Convert") { 85 | if (cb_i$Asset2 == "XTZ") { 86 | x$xtzSent <- 0 87 | x$xtzReceived <- as.numeric( 88 | str_replace(strsplit(cb_i$Notes, " ")[[1]][5], ",", "") 89 | ) 90 | x$costBasis <- cb_i$`Total (inclusive of fees and/or spread)` 91 | x$case <- "Coinbase convert (buy)" 92 | x$xtzBuy <- TRUE 93 | x$quote <- NA 94 | } 95 | else { 96 | x$xtzSent <- cb_i$`Quantity Transacted` 97 | x$xtzReceived <- 0 98 | x$costBasis <- NA 99 | x$case <- "Coinbase convert (sell)" 100 | x$xtzBuy <- FALSE 101 | x$quote <- cb_i$`Total (inclusive of fees and/or spread)` / cb_i$`Quantity Transacted` 102 | } 103 | } 104 | 105 | # Coinbase income 106 | else if (cb_i$`Transaction Type` == "Rewards Income") { 107 | x$xtzSent <- 0 108 | x$xtzReceived <- cb_i$`Quantity Transacted` 109 | x$costBasis <- 0 110 | x$case <- "Coinbase income" 111 | } 112 | 113 | cb_is %<>% 114 | bind_rows(., x) %>% 115 | mutate(., walletTx=FALSE) 116 | 117 | } 118 | 119 | # Identify and adjust Coinbase/wallet transfers 120 | drop_rows <- c() 121 | for (i in 1:nrow(cb_is)) { 122 | 123 | if (cb_is$case[i] == "Coinbase send") { 124 | time_i <- cb_is$timestamp[i] 125 | xtz_i <- cb_is$xtzSent[i] + cb_is$xtzReceived[i] 126 | is_i <- is %>% filter(., 127 | between(timestamp, time_i - 300, time_i + 300), 128 | between(xtzReceived + xtzFee, xtz_i - 0.1, xtz_i + 0.1) 129 | ) 130 | if (nrow(is_i) > 0) { 131 | id_i <- is_i$id[1] 132 | is[is$id == id_i, "xtzSent"] <- is[is$id == id_i, "xtzFee"] 133 | is[is$id == id_i, "xtzReceived"] <- 0 134 | is[is$id == id_i, "case"] <- cb_is$case[i] 135 | drop_rows <- c(drop_rows, i) 136 | } 137 | } 138 | 139 | else if (cb_is$case[i] == "Coinbase receive") { 140 | time_i <- cb_is$timestamp[i] 141 | xtz_i <- cb_is$xtzSent[i] + cb_is$xtzReceived[i] 142 | is_i <- is %>% filter(., 143 | between(timestamp, time_i - 300, time_i + 300), 144 | between(xtzSent - xtzFee, xtz_i - 0.1, xtz_i + 0.1) 145 | ) 146 | if (nrow(is_i) > 0) { 147 | id_i <- is_i$id[1] 148 | is[is$id == id_i, "xtzSent"] <- is[is$id == id_i, "xtzFee"] 149 | is[is$id == id_i, "xtzReceived"] <- 0 150 | is[is$id == id_i, "case"] <- cb_is$case[i] 151 | drop_rows <- c(drop_rows, i) 152 | } 153 | } 154 | 155 | } 156 | if (length(drop_rows) > 0) cb_is <- cb_is[-drop_rows, ] 157 | 158 | # Combine with income statement 159 | is %<>% 160 | bind_rows(., cb_is) %>% 161 | arrange(., timestamp) 162 | -------------------------------------------------------------------------------- /functions/classify_tx.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | # HEN contracts 20 | hen_contracts <- c( 21 | "KT1Hkg5qeNhfwpKW4fXvq7HGZB9z2EnmCCA9", 22 | "KT1HbQepzV1nVGg8QVznG7z4RcHseD5kwqBn", 23 | "KT1My1wDZHDGweCrJnQJi3wcFaS67iksirvj", 24 | "KT1TybhR7XraG75JFYKSrh7KnxukMBT5dor6", 25 | "KT1PHubm9HtyQEJ4BBpMTVomq6mhbfNZ9z5w" # Teia 26 | ) 27 | 28 | # QuipuSwap contracts 29 | quipu_contracts <- c( 30 | "KT1QxLqukyfohPV5kPkw97Rs6cw1DDDvYgbB", 31 | "KT1BMEEPX7MWzwwadW3NCSZe9XGmFJ7rs7Dr", 32 | "KT1X3zxdTzPB9DgVzA3ad6dgZe9JEamoaeRy", 33 | "KT1FHiJmJUgZMPtv5F8M4ZEa6cb1D9Lf758T", 34 | "KT1JyPE1BWdYoRGBvvKhEPbcVRd3C9NCCwQC", 35 | "KT1NEa7CmaLaWgHNi6LkRi5Z1f4oHfdzRdGA", 36 | "KT1FG63hhFtMEEEtmBSX2vuFmP87t9E7Ab4t", 37 | "KT1V41fGzkdTJki4d11T1Rp9yPkCmDhB7jph", 38 | "KT1Qm3urGqkRsWsovGzNb2R81c3dSfxpteHG", 39 | "KT1U2hs5eNdeCpHouAvQXGMzGFGJowbhjqmo", 40 | "KT1X6dAh8fwQMkWC9yh4yuvkJaS5NjqY4NvW", 41 | "KT1NXdxJkCiPkhwPvaT9CytFowuUoNcwGM1p", 42 | "KT1JzZtBeHDBS9XyEqUc9nZhsqcfeNLxVV1T", 43 | "KT1X6MJFtzypK6yuMXzg4KQ9FJAccH8PazKz", 44 | "KT1C755xS3TLH4HEWaCJvWuoHTcytTBdEjgS", 45 | "KT1Ji39TrkVBd6L2SL7H4u9yvA1sPuSygthq", 46 | "KT1K4EwTpbvYN9agJdjpyJm4ZZdhpUNKB3F6", 47 | "KT1R8eiRrSoSHufHXPqevZZYEUVzBxhcdenP", 48 | "KT1XmsFAdPhsDnhGzQLqQQiGkCGoyekGFC8B", 49 | "KT1ULad9tYBY39FT8K1Dgtha7jD3qpygbLPG", 50 | "KT1LZqFas2GEUe5z5CFkL9Wqq6tZFzQbopm8", 51 | "KT1Gdix8LoDoQng7YqdPNhdP5V7JRX8FqWvM", 52 | "KT1WREc3cpr36Nqjvegr6WSPgQKwDjL7XxLN", 53 | "KT1VEKgCCYCudjfvLZaEh4havFiJMmxMgNdX", 54 | "KT1Eg2QesN1tCzScTrvoeKm5W67GgjV32McR", 55 | "KT1Vjp7tHoNXRPpd9BbGrq9pbxF1FxMFUFTE", 56 | "KT1GFDagtGGQS1gmC3S6Noqns1svCTzL23By", 57 | "KT1DksKXvCBJN7Mw6frGj6y6F3CbABWZVpj1", 58 | "KT1ANY7962FTf2RqJMMF4paZkuTQA77994yv", 59 | "KT1RRgK6eXvCWCiEGWhRZCSVGzhDzwXEEjS4", 60 | "KT1ANEFHasacTTZxGPmQmZo7spj5YuLE6TL4", 61 | "KT1X1LgNkQShpF9nRLYw3Dgdy4qp38MX617z", 62 | "KT1A91CcMx1izXcbBwyH3z8Do3vnEdKpbde2", 63 | "KT1BgezWwHBxA9NrczwK9x3zfgFnUkc7JJ4b", 64 | "KT1Evsp2yA19Whm24khvFPcwimK6UaAJu8Zo", 65 | "KT1DuYujxrmgepwSDHtADthhKBje9BosUs1w", 66 | "KT19g5hey69CiXRbhRzJEwvuJ95RgVLzS3TP", 67 | "KT1RKdp1rL3c3wxy6XWE8ZdUXdihrGjb4eGB", 68 | "KT1VXBX6NwapYf9Sq6LsQVr4SdsDq3ta1nss", 69 | "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ", 70 | "KT1FptuULGK69mZRsBz62CSFdRs52etEb6Ah", 71 | "KT1V4jaZpCwhfitTnUucY1EHiRfz3bjqznAU", 72 | "KT1V4jaZpCwhfitTnUucY1EHiRfz3bjqznAU", 73 | "KT1Ti3nJT85vNn81Dy5VyNzgufkAorUoZ96q", 74 | "KT1WxgZ1ZSfMgmsSDDcUn8Xn577HwnQ7e1Lb", 75 | "KT1J3wTYb4xk5BsSBkg6ML55bX1xq7desS34", 76 | "KT1W3VGRUjvS869r4ror8kdaxqJAZUbPyjMT" 77 | ) 78 | 79 | # Crunchy contracts 80 | crunchy_contracts <- c( 81 | "KT1KnuE87q1EKjPozJ5sRAjQA24FPsP57CE3" 82 | ) 83 | 84 | # Tezos Domains contracts 85 | td_contracts <- c( 86 | "KT1P8n2qzJjwMPbHJfi4o8xu6Pe3gaU3u2A3", 87 | "KT191reDVKrLxU9rjTSxg53wRqj6zh8pnHgr", 88 | "KT1Mqx5meQbhufngJnUAGEGpa4ZRxhPSiCgB", 89 | "KT1GBZmSxmnKJXGMdMLbugPfLyUPmuLSMwKS", 90 | "KT1Evxe1udtPDGWrkiRsEN3vMDdB6gNpkMPM", 91 | "KT1EVYBj3f1rZHNeUtq4ZvVxPTs77wuHwARU" 92 | ) 93 | 94 | # OBJKT contracts 95 | objkt_contracts <- c( 96 | "KT1Dno3sQZwR5wUCWxzaohwuJwG3gX1VWj1Z", 97 | "KT1FvqJwEDWb1Gwc55Jd1jjTHRVWbYKUUpyq", 98 | "KT1XjcRq5MLAzMKQ3UHsrue2SeU2NbxUrzmU", 99 | "KT1QJ71jypKGgyTNtXjkCAYJZNhCKWiHuT2r", 100 | "KT1Aq4wWmVanpQhq4TTfjZXB5AjFpx15iQMM", 101 | "KT1Wvk8fon9SgNEPQKewoSL2ziGGuCQebqZc" 102 | ) 103 | 104 | # OBJKT v2 contracts 105 | objkt_v2_contracts <- c( 106 | "KT1WvzYHCNBvDSdwafTHv7nJ1dWmZ8GCYuuC", 107 | "KT18p94vjkkHYY3nPmernmgVR7HdZFzE7NAk", 108 | "KT1TjnZYs5CGLbmV6yuW169P8Pnr9BiVwwjz", 109 | "KT1CePTyk6fk4cFr6fasY5YXPGks6ttjSLp4", # 2024 110 | "KT1Xjap1TwmDR1d8yEd8ErkraAj2mbdMrPZY", 111 | "KT1SwbTqhSKF6Pdokiu1K4Fpi17ahPPzmt1X" 112 | ) 113 | 114 | # akaSwap contracts 115 | aka_contracts <- c( 116 | "KT1HGL8vx7DP4xETVikL4LUYvFxSV19DxdFN", 117 | "KT1NL8H5GTAWrVNbQUxxDzagRAURsdeV3Asz", 118 | "KT1ULea6kxqiYe1A7CZVfMuGmTx7NmDGAph1", 119 | "KT19QcybJCf8zKCEECRhtMUYTptTwgY9jMKU", 120 | "KT1Dn3sambs7KZGW88hH2obZeSzfmCmGvpFo", 121 | "KT1J2C7BsYNnSjQsGoyrSXShhYGkrDDLVGDd" 122 | ) 123 | 124 | # typed contracts 125 | typed_contracts <- c( 126 | "KT1VoZeuBMJF6vxtLqEFMoc4no5VDG789D7z" 127 | ) 128 | 129 | # 8scribo contracts 130 | scribo_contracts <- c( 131 | "KT19vw7kh7dzTRxFUZNWu39773baauzNWtzj" 132 | ) 133 | 134 | # C-VERSO contracts 135 | cverso_contracts <- c( 136 | "KT1BJaN9oY2SuUzwACxSegGJynkrRbQCEEfX", 137 | "KT1FVqzFUCD8LZAEeoNG3YsW1hkNnHNHMsKt" 138 | ) 139 | 140 | # emprops contracts 141 | emprops_contracts <- c( 142 | "KT1P5k64GTB8PPPyB1eb2wCuVyUSdPEB5gZN", 143 | "KT1JdFrjfRrhtwVUKWzFX5W1KB1F7koXThu9", 144 | "KT1APUSYNqiwNaE1ZrZ1eiT3HsAKsiHnjTjd" 145 | ) 146 | 147 | # fxhash contracts 148 | fx_contracts <- c( 149 | "KT1AEVuykWeuuFX7QkEAMNtffzwhe1Z98hJS", 150 | "KT1XCoGnfupWk7Sp8536EfrxcP73LmT68Nyr", 151 | "KT1Xo5B7PNBAeynZPmca4bRh6LQow4og1Zb9", 152 | "KT1Ezht4PDKZri7aVppVGT4Jkw39sesaFnww" 153 | ) 154 | 155 | #fxhash v2 contracts 156 | fx_v2_contracts <- c( 157 | "KT1BJC12dG17CVvPKJ1VYaNnaT5mzfnUTwXv", 158 | "KT1GbyoDi7H1sfXmimXpptZJuCdHMh66WS9u", 159 | "KT1M1NyU9X4usEimt2f3kDaijZnDMNBu42Ja" #fxtext 160 | ) 161 | 162 | # Rarible contracts 163 | rari_contracts <- c( 164 | "KT198mqFKkiWerXLmMCw69YB1i6yzYtmGVrC", 165 | "KT18pVpRXKPY2c4U2yFEGSH3ZnhB2kL8kwXS" 166 | ) 167 | 168 | # NFTbutton contracs 169 | nftbutton_contracts <- c( 170 | "KT1Ax9VNx2fqnknLXDAgXt3b3amkBQTR62Tj", 171 | "KT1RtfsNjwQoNATiM53ZD621disELccxpyjE", 172 | "KT1Ax9VNx2fqnknLXDAgXt3b3amkBQTR62Tj" 173 | ) 174 | 175 | wrap_contracts <- c( 176 | "KT1DLif2x9BtK6pUq9ZfFVVyW5wN2kau9rkW", 177 | "KT1MTnKjFSN2u4myPRBqnFuCYCPX8kTdUEnv" 178 | ) 179 | 180 | kolibri_contracts <- c( 181 | "KT1Mgy95DVzqVBNYhsW93cyHuB57Q94UFhrh", 182 | "KT1DLaeYVgg4X21BFyFgJ8gjcR3AnPNM8ZCY" 183 | 184 | ) 185 | 186 | versum_contracts <- c( 187 | "KT1GyRAJNdizF1nojQz62uGYkx8WFRUJm9X5", 188 | "KT1KRvNVubq64ttPbQarxec5XdS6ZQU4DVD2", 189 | "KT1NUrzs7tiT4VbNPqeTxgAFa4SXeV1f3xe9" 190 | ) 191 | 192 | minterpop_contracts <- c( 193 | "KT1DgUawhCMBixK8Nt24uvMxFdjYRRbjiNGi" 194 | ) 195 | 196 | eightbidou_contracts <- c( 197 | "KT1BvWGFENd4CXW5F3u4n31xKfJhmBGipoqF", 198 | "KT1AHBvSo828QwscsjDjeUuep7MgApi8hXqA", 199 | "KT1QtnHR8p2hBjUhPRy9BCWgy7s7L578PA7N" 200 | ) 201 | 202 | endless_ways_contracts <- c( 203 | "KT1VdCrmZsQfuYgbQsezAHT1pXvs6zKF8xHB" 204 | ) 205 | 206 | cverso_contracts <- c( 207 | "KT1BJaN9oY2SuUzwACxSegGJynkrRbQCEEfX" 208 | ) 209 | 210 | vending_contracts <- c( 211 | "KT1UzjDKju7P372WTLvCiEGoVRCskfoQ4V8A" 212 | ) 213 | 214 | # Create null income statement 215 | is <- operations[0, ] 216 | 217 | # Identify unique operation groups 218 | operations_hash <- operations %>% distinct(., hash) 219 | 220 | # Adjust operation data 221 | operations %<>% mutate(., bidKey = "") 222 | 223 | # Iterate over unique operation groups to build income statement 224 | for (i in 1:nrow(operations_hash)) { 225 | 226 | ############################################################################## 227 | # Define variables, as necessary 228 | ############################################################################## 229 | 230 | # Helper variables, hash adjustment, etc 231 | x <- operations %>% 232 | filter(., hash == operations_hash[[1]][i]) %>% 233 | mutate(., hash = str_split(hash, "_", simplify=TRUE)[, 1]) 234 | y <- x 235 | 236 | # Update token data 237 | if (sum(c("transfer", "mint") %in% x$parameterEntry) > 0) { 238 | for (i in 1:nrow(x)) { 239 | 240 | # Add token metadata, if possible 241 | if (sum(c("transfer", "mint") %in% x$parameterEntry[i]) > 0) { 242 | x$tokenID[i] <- paste0(x$targetAddress[i], "_", list_check(x$parameterValue[i], c("token_id", "itokenid"))) 243 | x$tokenSender[i] <- list_check(x$parameterValue[i], c("address", "from_", "from")) 244 | x$tokenReceiver[i] <- list_check(x$parameterValue[i], c("to_", "to")) 245 | x$tokenAmount[i] <- as.numeric(list_check(x$parameterValue[i], c("amount", "value"))) 246 | } 247 | } 248 | } 249 | x$xtzSent <- max(x$xtzSent, na.rm=TRUE) 250 | x$xtzReceived <- sum(x$xtzReceived) 251 | xtzCollect <- sort(x$xtzAmount, decreasing=TRUE)[2] 252 | xtzCollect_bid <- sort(x$xtzAmount, decreasing=TRUE)[1] 253 | 254 | ############################################################################## 255 | # Identify operation groups, filter and classify as necessary 256 | ############################################################################## 257 | 258 | # Adjust reveals 259 | if (("reveal" %in% x$type) & (nrow(x) > 1)) { 260 | x %<>% filter(., type != "reveal") 261 | } 262 | 263 | # Failed transaction 264 | if (sum(c("failed", "backtracked") %in% x$status) > 0) { 265 | x %<>% quick_case(., case="Failed transaction", type=2) 266 | x %<>% mutate(., tokenAmount=0) 267 | } 268 | 269 | # Adjust delegation operations 270 | else if ("delegation" %in% x$type) { 271 | x %<>% quick_case(., case="Delegation", type=2) 272 | } 273 | 274 | # Adjust other non-transactionary operations 275 | else if (!("transaction" %in% x$type)) { 276 | x %<>% quick_case(., case="Non-transaction", type=2) 277 | } 278 | 279 | # Standard transaction 280 | else if (sum(is.na(x$parameterEntry)) == nrow(x)) { 281 | x %<>% 282 | filter(., (SenderAddress %in% wallets) | (targetAddress %in% wallets)) %>% 283 | mutate(., case = "Standard transaction") 284 | 285 | # Adjust wallet-to-wallet transfers 286 | if ( 287 | (sum(x$SenderAddress %in% wallets) > 0) & 288 | (sum(x$targetAddress %in% wallets) > 0) 289 | ) { 290 | x %<>% mutate(., xtzSent=xtzFee, xtzReceived=0, case="Wallet transfer") 291 | } 292 | 293 | } 294 | 295 | # Token transfer 296 | else if (nrow(x) == sum(x$parameterEntry == "transfer", na.rm=TRUE)) { 297 | x %<>% mutate(., tokenSender = SenderAddress, case = "Token transfer") 298 | 299 | # Adjust fxhash v2 batch transfers 300 | x_temp <- x 301 | for (i in 1:nrow(x)) { 302 | if (x$targetAddress[i] == "KT1U6EHmNxJTkvaWJ4ThczG4FSDaHC21ssvi") { 303 | x_i <- x[i,] 304 | tx_i = x_i$parameterValue[[1]]$txs[[1]] 305 | if (length(tx_i) > 0) { 306 | for (j in 1:nrow(tx_i)) { 307 | x_j <- x_i 308 | tx_j <- tx_i[j,] 309 | x_j$tokenID = str_c(x_j$targetAddress, "_", tx_j$token_id) 310 | x_j$tokenAmount = as.numeric(tx_j$amount) 311 | x_temp <- bind_rows(x_temp, x_j) 312 | } 313 | } 314 | } 315 | } 316 | x <- x_temp 317 | x %<>% mutate(., 318 | xtzFee = xtzFee / nrow(.), 319 | xtzSent = xtzFee, 320 | tokenAmount = ifelse(is.na(tokenAmount), 0, tokenAmount) 321 | ) %>% 322 | distinct(.) 323 | 324 | # Adjust wallet-to-wallet transfers 325 | for (i in 1:nrow(x)) 326 | if ((x$tokenSender[i] %in% wallets) & (x$tokenReceiver[i] %in% wallets)) { 327 | x$tokenAmount[i] = 0 328 | x$case[i] = "Wallet transfer" 329 | } 330 | 331 | } 332 | 333 | # Contract signature 334 | else if ((nrow(x) == 1) & ("sign" %in% x$parameterEntry)) { 335 | x %<>% mutate(., case="Contract signature") 336 | } 337 | 338 | # HEN contracts 339 | else if (sum(hen_contracts %in% x$targetAddress) > 0) { 340 | 341 | # HEN mint 342 | if ("mint" %in% x$parameterEntry) { 343 | x %<>% 344 | filter(., parameterEntry == "mint") %>% 345 | mutate(., 346 | tokenSender = NA, 347 | tokenReceiver = initiatorAddress, 348 | case="HEN mint" 349 | ) 350 | } 351 | 352 | # HEN swap 353 | else if ("swap" %in% x$parameterEntry) { 354 | x %<>% quick_case(., entry="swap", case="HEN swap") 355 | } 356 | 357 | # HEN cancel swap 358 | else if ("cancel_swap" %in% x$parameterEntry) { 359 | x %<>% quick_case(., entry="cancel_swap", case="HEN cancel swap") 360 | } 361 | 362 | # HEN trade 363 | else if ( 364 | ("collect" %in% x$parameterEntry) & 365 | (sum(wallets %in% x$initiatorAddress) == 0) 366 | ) { 367 | n_collect <- sum(x$parameterEntry == "collect", na.rm=TRUE) 368 | if (n_collect == 1) { 369 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 370 | x %<>% 371 | filter(., parameterEntry == "transfer") %>% 372 | mutate(., 373 | tokenAmount = ifelse(xtzCollect != xtzReceived, 0, tokenAmount), 374 | tokenSender = ifelse(xtzCollect != xtzReceived, NA, token_sender), 375 | case = ifelse( 376 | xtzCollect != xtzReceived, 377 | "HEN collect (sales/royalties)", 378 | "HEN collect (trade)" 379 | ) 380 | ) 381 | } 382 | else { 383 | x <- y 384 | } 385 | } 386 | 387 | # HEN collect 388 | else if ( 389 | ("collect" %in% x$parameterEntry) & 390 | (sum(wallets %in% x$initiatorAddress) > 0) 391 | ) { 392 | x %<>% quick_case(., entry="transfer", case="HEN collect") 393 | } 394 | 395 | # HEN curate 396 | else if ("curate" %in% x$parameterEntry) { 397 | x %<>% 398 | quick_case(., case="HEN curate", type=2) %>% 399 | mutate(., 400 | tokenID = "KT1AFA2mwNUMNd4SsujE1YYp29vd8BZejyKW_0", 401 | tokenAmount = as.numeric(list_check(parameterValue, "hDAO_amount")), 402 | tokenSender = initiatorAddress 403 | ) 404 | } 405 | 406 | # HEN claim hDAO 407 | else if ("claim_hDAO" %in% x$parameterEntry) { 408 | x %<>% quick_case(., entry="transfer", case="HEN claim hDAO") 409 | } 410 | 411 | # HEN registry 412 | else if ("registry" %in% x$parameterEntry) { 413 | x %<>% quick_case(., entry="registry", case="HEN registry") 414 | } 415 | 416 | # HEN unidentified 417 | else { 418 | x <- y 419 | } 420 | } 421 | 422 | # Crunchy contracts 423 | else if (sum(crunchy_contracts %in% x$targetAddress) > 0) { 424 | 425 | # Crunchy harvest 426 | if ("harvest" %in% x$parameterEntry) { 427 | 428 | if ("transfer" %in% x$parameterEntry) { 429 | x %<>% quick_case(., entry="transfer", case="Crunchy harvest") 430 | } 431 | else { 432 | x %<>% quick_case(., case="Crunchy harvest (No reward)", type=2) 433 | } 434 | 435 | } 436 | 437 | # Crunchy deposit 438 | else if ("deposit" %in% x$parameterEntry) { 439 | 440 | n_transfers <- nrow(filter(x, parameterEntry == "transfer")) 441 | 442 | x %<>% 443 | filter(., parameterEntry == "transfer") %>% 444 | mutate(., 445 | xtzSent = xtzFee / n_transfers, 446 | tokenAmount = ifelse(tokenSender %in% wallets, 0, tokenAmount), 447 | case = "Crunchy deposit", 448 | ) 449 | 450 | } 451 | 452 | # Crunchy withdrawal 453 | else if ("withdrawal" %in% x$parameterEntry) { 454 | 455 | n_transfers <- nrow(filter(x, parameterEntry == "transfer")) 456 | 457 | x1 <- x %>% 458 | filter(., parameterEntry == "transfer") %>% 459 | mutate(., 460 | xtzSent = xtzFee / n_transfers, 461 | tokenAmount = ifelse(row_number() == 1, 0, tokenAmount), 462 | case = "Crunchy withdrawal" 463 | ) 464 | 465 | if (x$xtzReceived[1] > 0) { 466 | x2 <- x %>% 467 | filter(., is.na(parameterEntry)) %>% 468 | mutate(., 469 | xtzSent = 0, 470 | case = "Crunchy baking payment" 471 | ) 472 | x <- bind_rows(x1, x2) 473 | } 474 | else { 475 | x <- x1 476 | } 477 | 478 | } 479 | 480 | # Crunchy unidentified 481 | else { 482 | x <- y 483 | } 484 | 485 | } 486 | 487 | # QuipuSwap contracts 488 | else if (sum(quipu_contracts %in% x$targetAddress) > 0) { 489 | 490 | # Drop extra approval data 491 | if (("approve" %in% x$parameterEntry) & (nrow(x) > 1)) { 492 | x %<>% mutate(., xtzSent = xtzSent - xtzFee / 2) 493 | } 494 | 495 | # QuipuSwap trade 496 | if (sum(c("tezToTokenPayment", "tokenToTezPayment") %in% x$parameterEntry) > 0) { 497 | x %<>% quick_case(., entry="transfer", case="QuipuSwap trade") 498 | } 499 | 500 | # QuipuSwap invest liquidity 501 | else if ("investLiquidity" %in% x$parameterEntry) { 502 | 503 | x1 <- x %>% 504 | filter(., parameterEntry == "investLiquidity") %>% 505 | mutate(., 506 | xtzProceeds = quote * (xtzSent - (xtzFee / 2.0)), 507 | xtzSent = xtzSent - (xtzFee / 2.0), 508 | case = "QuipuSwap invest liquidity" 509 | ) 510 | 511 | x2 <- x %>% 512 | filter(., parameterEntry == "transfer") %>% 513 | mutate(., 514 | tokenProceeds = quote * (xtzSent - (xtzFee / 2.0)), 515 | xtzSent = xtzFee / 2.0, 516 | case = "QuipuSwap invest liquidity" 517 | ) 518 | 519 | # Find LP token amount 520 | tx_id <- x1$id[1] 521 | tx_hash <- x1$hash[1] 522 | tx_operations <- tzkt_operations_hash(tx_hash, quote=currency) 523 | tx_operations %<>% filter(., id == tx_id) 524 | 525 | if (tx_operations$diffs[[1]][2,]$action == "add_key") { 526 | token_received <- as.numeric(tx_operations$diffs[[1]][2,]$content$value$balance) 527 | } 528 | else { 529 | 530 | id <- tx_operations$diffs[[1]][2, ]$bigmap 531 | key <- tx_operations$diffs[[1]][2, ]$content$key 532 | bigmap <- tzkt_bigmap_updates(id=id, key=key) 533 | 534 | bigmap$balance <- as.numeric(bigmap$value$balance) 535 | bigmap$frozen_balance <- as.numeric(bigmap$value$frozen_balance) 536 | 537 | bigmap %<>% 538 | mutate(., balance = balance + frozen_balance) %>% 539 | group_by(., level) %>% 540 | filter(., row_number() >= (n() - 1)) %>% 541 | ungroup(.) %>% 542 | mutate(., delta = balance - lag(balance)) %>% 543 | filter(., level == x1$level[1]) 544 | 545 | token_received <- bigmap$delta[1] 546 | 547 | } 548 | 549 | x3 <- x %>% 550 | filter(., parameterEntry == "investLiquidity") %>% 551 | mutate(., 552 | costBasis = 2.0 * quote * (xtzSent - (xtzFee / 2.0)), 553 | xtzSent = 0, 554 | tokenAmount = token_received, 555 | tokenReceiver = x2$initiatorAddress[1], 556 | tokenID = paste0(x2$tokenID[1], "_LP"), 557 | case = "QuipuSwap invest liquidity" 558 | ) 559 | 560 | x <- bind_rows(x1, x2, x3) 561 | 562 | } 563 | 564 | # QuipuSwap divest liquidity 565 | else if ("divestLiquidity" %in% x$parameterEntry) { 566 | 567 | x1 <- x %>% 568 | filter(., is.na(parameterEntry)) %>% 569 | mutate(., 570 | xtzBuy = TRUE, 571 | case = "QuipuSwap divest liquidity" 572 | ) 573 | 574 | x2 <- x %>% 575 | filter(., parameterEntry == "transfer") %>% 576 | mutate(., 577 | xtzSent = 0, 578 | xtzReceived = 0, 579 | costBasis = quote * x1$xtzReceived[1], 580 | case = "QuipuSwap divest liquidity" 581 | ) 582 | 583 | x3 <- x %>% 584 | filter(., parameterEntry == "divestLiquidity") %>% 585 | mutate(., 586 | xtzSent = 0, 587 | xtzReceived = 0, 588 | tokenSender = SenderAddress, 589 | tokenAmount = as.numeric(list_check(parameterValue, "shares")), 590 | tokenID = paste0(x2$tokenID[1], "_LP"), 591 | tokenProceeds = 2 * quote * x1$xtzReceived[1], 592 | case = "QuipuSwap divest liquidity" 593 | ) 594 | 595 | x <- bind_rows(x1, x2, x3) 596 | 597 | } 598 | 599 | # QuipuSwap vote 600 | else if ("vote" %in% x$parameterEntry) { 601 | x %<>% quick_case(., entry="vote", case="QuipuSwap vote") 602 | } 603 | 604 | # QuipuSwap wWETH wrap 605 | else if ("mint_erc20" %in% x$parameterEntry) { 606 | z <- x %>% filter(., parameterEntry == "mint_tokens") 607 | wethAmount <- as.numeric(z$parameterValue[[1]]$amount[1]) 608 | ethQuote <- tzkt_quote(level=z$level) 609 | cb <- wethAmount / 1000000000000000000 * ethQuote[1,currency] / ethQuote$eth 610 | x %<>% 611 | filter(., parameterEntry == "mint_tokens") %>% 612 | mutate(., 613 | case = "Wrap WETH", 614 | tokenReceiver = wallets[1], 615 | tokenAmount = wethAmount, 616 | costBasis = cb, 617 | tokenID = "KT18fp5rcTW7mbWDmzFwjLDUhs5MeJmagDSZ/20" 618 | ) 619 | } 620 | 621 | # QuipuSwap deposit 622 | else if ("deposit" %in% x$parameterEntry) { 623 | x %<>% 624 | filter(., parameterEntry == "transfer") %>% 625 | mutate(., 626 | case = "QuipuSwap deposit", 627 | tokenAmount = 0, 628 | tokenID = NA 629 | ) 630 | } 631 | 632 | # QuipuSwap withdraw 633 | else if ("withdraw" %in% x$parameterEntry) { 634 | x %<>% 635 | filter(., parameterEntry == "transfer") %>% 636 | mutate(., 637 | case = "QuipuSwap withdraw", 638 | tokenAmount = 0, 639 | tokenID = NA 640 | ) 641 | } 642 | 643 | # Quipuswap unidentified 644 | else { 645 | x <- y 646 | } 647 | } 648 | 649 | #0xSelfie contract 650 | else if (sum("KT1LMrt6NKe86GUeCxHXjXkf5UD4e5uUTSkP" %in% x$targetAddress) > 0) { 651 | 652 | # 0xSelfie mint 653 | if ("mint" %in% x$parameterEntry) { 654 | x %<>% quick_case(., case="0xSelfie mint", type=2) 655 | } 656 | 657 | # 0xSelfie access 658 | else if ("request_access" %in% x$parameterEntry) { 659 | x %<>% quick_case(., case="0xSelfie access", type=2) 660 | } 661 | 662 | # 0xSelfie unidentified 663 | else { 664 | x <- y 665 | } 666 | 667 | } 668 | 669 | # Marina hero mint 670 | else if (sum("KT1Q2jUJnrvrrhi4gBpZVLm37nyCqaFNtK7X" %in% x$targetAddress) > 0) { 671 | 672 | # Marina pay 673 | if ("default" %in% x$parameterEntry) { 674 | x %<>% quick_case(., entry="default", case="Marina payment") 675 | } 676 | 677 | # Marina unidentified 678 | else { 679 | x <- y 680 | } 681 | 682 | } 683 | 684 | # Tezos Domains contracts 685 | else if (sum(td_contracts %in% x$targetAddress) > 0) { 686 | 687 | # TD commit 688 | if ("commit" %in% x$parameterEntry) { 689 | x %<>% quick_case(., entry="commit", case="TD commit") 690 | } 691 | 692 | # TD buy 693 | else if ("buy" %in% x$parameterEntry) { 694 | x %<>% quick_case(., entry="buy", case="TD buy") 695 | } 696 | 697 | # TD sell 698 | else if ( 699 | ("execute_offer" %in% x$parameterEntry) & 700 | (sum(wallets %in% x$initiatorAddress) == 0) 701 | ) { 702 | x %<>% quick_case(., entry="transfer", case="TD sell (market)") 703 | } 704 | 705 | # TD buy 706 | else if ( 707 | ("execute_offer" %in% x$parameterEntry) & 708 | (sum(wallets %in% x$initiatorAddress) > 0) 709 | ) { 710 | x %<>% quick_case(., entry="transfer", case="TD buy (market)") 711 | } 712 | 713 | # TD update record 714 | else if ("update_record" %in% x$parameterEntry) { 715 | x %<>% quick_case(., entry="update_record", case="TD update record") 716 | } 717 | 718 | # TD update reverse record 719 | else if ("update_reverse_record" %in% x$parameterEntry) { 720 | x %<>% quick_case(., entry="update_reverse_record", case="TD update reverse record") 721 | } 722 | 723 | # TD place offer 724 | else if ("place_offer" %in% x$parameterEntry) { 725 | x %<>% quick_case(., entry="place_offer", case="TD place offer") 726 | } 727 | 728 | # TD renew 729 | else if ("renew" %in% x$parameterEntry) { 730 | x %<>% quick_case(., entry="renew", case="TD renew") 731 | } 732 | 733 | # TD remove offer 734 | else if ("remove_offer" %in% x$parameterEntry) { 735 | x %<>% quick_case(., entry="remove_offer", case="TD remove offer") 736 | } 737 | 738 | # TD update operators 739 | else if ("update_operators" %in% x$parameterEntry) { 740 | x %<>% quick_case(., entry="update_operators", case="TD update operators") 741 | } 742 | 743 | # TD unidentified 744 | else { 745 | x<- y 746 | } 747 | } 748 | 749 | # OBJKT contracts 750 | else if (sum(objkt_contracts %in% x$targetAddress) > 0) { 751 | 752 | # OBJKT ask 753 | if ("ask" %in% x$parameterEntry) { 754 | x %<>% quick_case(., entry="ask", case="OBJKT ask") 755 | } 756 | 757 | # OBJKT retract ask 758 | else if ("retract_ask" %in% x$parameterEntry) { 759 | x %<>% quick_case(., entry="retract_ask", case="OBJKT retract ask") 760 | } 761 | 762 | # OBJKT retract bid 763 | else if ("retract_bid" %in% x$parameterEntry) { 764 | x %<>% 765 | top_n(., n=-1, wt=id) %>% 766 | mutate(., 767 | tokenAmount = 0, 768 | xtzSent = xtzSent / 2, 769 | xtzFee = xtzFee / 2, 770 | xtzReceived = 0, 771 | case = "OBJKT retract bid" 772 | ) 773 | 774 | # Check if bid recorded, zero out if so 775 | tx_id <- x$id[1] 776 | tx_hash <- x$hash[1] 777 | tx_operations <- tzkt_operations_hash(tx_hash, quote=currency) 778 | tx_operations %<>% filter(., type == "transaction") 779 | id <- tx_operations$diffs[[1]]$bigmap 780 | key <- tx_operations$diffs[[1]]$content$hash 781 | 782 | if ((!is.na(key)) & (key %in% is$bidKey)) { 783 | is %<>% mutate(., 784 | tokenAmount = ifelse(bidKey == key & case != "OBJKT win auction (6210)", 0, tokenAmount), 785 | xtzSent = ifelse(bidKey == key & case != "OBJKT win auction (6210)", 0, xtzSent), 786 | xtzReceived = ifelse(bidKey == key & case != "OBJKT win auction (6210)", 0, xtzReceived), 787 | case = ifelse(bidKey == key & case != "OBJKT win auction (6210)", "OBJKT retract bid", case) 788 | ) 789 | } 790 | 791 | } 792 | 793 | # OBJKT bid 794 | else if ("bid" %in% x$parameterEntry) { 795 | x %<>% 796 | top_n(., n=-1, wt=id) %>% 797 | mutate(., 798 | xtzReceived = ifelse(SenderAddress %in% wallets, 0, 0), 799 | xtzSent = ifelse(SenderAddress %in% wallets, xtzSent - xtzAmount, 0), 800 | case = ifelse(SenderAddress %in% wallets, "OBJKT bid", "OBJKT outbid") 801 | ) 802 | 803 | # Check if auction was won 804 | tx_id <- x$id[1] 805 | tx_hash <- x$hash[1] 806 | tx_operations <- tzkt_operations_hash(tx_hash, quote=currency) 807 | tx_operations %<>% filter(., id == tx_id) 808 | id <- tx_operations$diffs[[1]]$bigmap 809 | key <- tx_operations$diffs[[1]]$content$key 810 | bigmap <- tzkt_bigmap(id=id, key=key) 811 | 812 | # Omit early OBJKT contracts 813 | if (x$targetAddress[1] != "KT1Dno3sQZwR5wUCWxzaohwuJwG3gX1VWj1Z") { 814 | 815 | if (id == 6210) { 816 | state <- as.numeric(bigmap$value$state) 817 | price <- as.numeric(bigmap$value$current_price) / 1000000 818 | buyer <- bigmap$value$highest_bidder 819 | time <- bigmap$value$end_time 820 | token_id <- paste0(bigmap$value$fa2, "_", bigmap$value$objkt_id) 821 | bidHash <- NA 822 | bidCase <- "OBJKT win auction (6210)" 823 | } 824 | else if (id == 5910) { 825 | state <- ifelse(bigmap$active == "FALSE", 2, 1) 826 | price <- as.numeric(bigmap$value$xtz_per_objkt) / 1000000 827 | buyer <- bigmap$value$issuer 828 | time <- tzkt_block(bigmap$lastLevel)$timestamp 829 | token_id <- paste0(bigmap$value$fa2, "_", bigmap$value$objkt_id) 830 | bidHash <- tx_operations$diffs[[1]]$content$hash 831 | bidCase <- "OBJKT win auction (5910)" 832 | } 833 | else { 834 | warning(cat("\nOBJKT auction contract not recognized:", tx_hash)) 835 | state <- NA 836 | } 837 | 838 | if ( 839 | (state == 2) & 840 | (as.Date(time) <= as.Date(date_span[2])) & 841 | (buyer %in% wallets) & 842 | (price == x$xtzAmount[[1]]) 843 | ) { 844 | x2 <- mutate(x, 845 | timestamp = time, 846 | quote = tx_operations$quote[[1]], 847 | xtzSent = price, 848 | xtzReceived = 0, 849 | tokenID = token_id, 850 | tokenAmount = 1, 851 | tokenReceiver= buyer, 852 | case = bidCase, 853 | bidKey = bidHash 854 | ) 855 | x %<>% bind_rows(., x2) 856 | } 857 | } 858 | } 859 | 860 | # OBJKT win auction (old contract) 861 | else if ( 862 | ("swap" %in% x$parameterEntry) & 863 | ("KT1Dno3sQZwR5wUCWxzaohwuJwG3gX1VWj1Z" %in% x$targetAddress) 864 | ) { 865 | x %<>% quick_case(., entry="transfer", case="OBJKT win auction (old)") 866 | tx_hash <- x$hash[1] 867 | tx_operations <- tzkt_operations_hash(tx_hash, quote=currency) 868 | tx_operations %<>% filter(., tx_operations$parameter$entrypoint == "swap") 869 | price <- as.numeric(tx_operations$diffs[[1]]$content$value$xtz_per_objkt) / 1000000 870 | x %<>% mutate(., xtzSent = price) 871 | } 872 | 873 | # OBJKT fulfill ask (trade) 874 | else if ( 875 | ("fulfill_ask" %in% x$parameterEntry) & 876 | (sum(wallets %in% x$initiatorAddress) == 0) 877 | ) { 878 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 879 | x %<>% 880 | filter(., parameterEntry == "transfer") %>% 881 | mutate(., 882 | tokenAmount = ifelse(xtzCollect != xtzReceived, 0, tokenAmount), 883 | tokenSender = ifelse(xtzCollect != xtzReceived, NA, token_sender), 884 | case = ifelse( 885 | xtzCollect != xtzReceived, 886 | "OBJKT fulfill ask (sales/royalties)", 887 | "OBJKT fulfill ask (trade)" 888 | ) 889 | ) 890 | } 891 | 892 | # OBJKT fulfill ask (collect) 893 | else if ( 894 | ("fulfill_ask" %in% x$parameterEntry) & 895 | (sum(wallets %in% x$initiatorAddress) > 0) 896 | ) { 897 | x %<>% quick_case(., entry="transfer", case="OBJKT fulfill ask (collect)") 898 | } 899 | 900 | # OBJKT fulfill bid (trade) 901 | else if ( 902 | ("fulfill_bid" %in% x$parameterEntry) & 903 | (sum(wallets %in% x$initiatorAddress) > 0) 904 | ) { 905 | token_sender <- x$initiatorAddress[which(x$targetAddress %in% wallets)][1] 906 | x %<>% 907 | filter(., parameterEntry == "transfer") %>% 908 | mutate(., 909 | tokenAmount = ifelse(xtzCollect_bid != xtzReceived, 0, tokenAmount), 910 | tokenSender = ifelse(xtzCollect_bid != xtzReceived, NA, token_sender), 911 | xtzSent = xtzFee, 912 | case = ifelse( 913 | xtzCollect != xtzReceived, 914 | "OBJKT fulfill bid (sales/royalties)", 915 | "OBJKT fulfill bid (trade)" 916 | ) 917 | ) 918 | } 919 | 920 | # OBJKT fulfill bid (collect) 921 | else if ( 922 | ("fulfill_bid" %in% x$parameterEntry) & 923 | (sum(wallets %in% x$initiatorAddress) == 0) 924 | ) { 925 | x %<>% quick_case(., entry="transfer", case="OBJKT fulfill bid (collect)") 926 | } 927 | 928 | # OBJKT buy dutch auction 929 | else if ( 930 | ("buy" %in% x$parameterEntry) & 931 | (sum(wallets %in% x$initiatorAddress) > 0) 932 | ) { 933 | x %<>% quick_case(., entry="transfer", case="OBJKT buy dutch auction") 934 | } 935 | 936 | # OBJKT create auction 937 | else if("create_auction" %in% x$parameterEntry) { 938 | x %<>% quick_case(., entry="create_auction", case="OBJKT create auction") 939 | } 940 | 941 | # OBJKT conclude auction 942 | else if("conclude_auction" %in% x$parameterEntry) { 943 | x %<>% quick_case(., entry="conclude_auction", case="OBJKT conclude auction") 944 | } 945 | 946 | # OBJKT cancel auction 947 | else if("cancel_auction" %in% x$parameterEntry) { 948 | x %<>% quick_case(., entry="cancel_auction", case="OBJKT cancel auction") 949 | } 950 | 951 | # OBJKT create collection 952 | else if ("create_artist_collection" %in% x$parameterEntry) { 953 | x %<>% quick_case(., entry="create_artist_collection", case="OBJKT create collection") 954 | } 955 | 956 | # OBJKT mint 957 | else if ("mint_artist" %in% x$parameterEntry) { 958 | x %<>% 959 | filter(., parameterEntry == "mint") %>% 960 | mutate(., 961 | tokenSender = NA, 962 | tokenReceiver = initiatorAddress, 963 | case = "OBJKT mint" 964 | ) 965 | } 966 | 967 | # OBJKT update metadata 968 | else if ("update_artist_metadata" %in% x$parameterEntry) { 969 | x %<>% quick_case(., entry="update_artist_metadata", case="OBJKT update metadata") 970 | } 971 | 972 | # OBJKT unidentified 973 | else { 974 | x <- y 975 | } 976 | } 977 | 978 | # OBJKT v2 contracts 979 | else if (sum(objkt_v2_contracts %in% x$targetAddress) > 0) { 980 | 981 | # OBJK v2 ask 982 | if ("ask" %in% x$parameterEntry) { 983 | x %<>% quick_case(., entry="ask", case="OBJKT v2 ask") 984 | } 985 | 986 | # OBJKT v2 retract ask 987 | else if ("retract_ask" %in% x$parameterEntry) { 988 | x %<>% quick_case(., entry="retract_ask", case="OBJKT v2 retract ask") 989 | } 990 | 991 | # OBJKT v2 offer 992 | else if ("offer" %in% x$parameterEntry) { 993 | x_hash <- tzkt_operations_hash(hash=x$hash[1], quote=currency) 994 | token_id <- str_c(x_hash[1,]$parameter$value$token$address, "_", x_hash[1,]$parameter$value$token$token_id) 995 | token_amount <- as.numeric(x_hash[1,]$parameter$value$amount) 996 | 997 | bm_id <- x_hash[1,]$diffs[[1]]$bigmap 998 | bm_key <- x_hash[1,]$diffs[[1]]$content$key 999 | bm_updates <- tzkt_bigmap_updates(bm_id, bm_key) 1000 | 1001 | bm_last_update <- bm_updates[nrow(bm_updates),] 1002 | bm_last_action <- bm_last_update$action 1003 | bm_last_action_date <- as_datetime(bm_last_update$timestamp) 1004 | bm_last_level <- bm_last_update$level 1005 | 1006 | ########################################################################## 1007 | # Check for key removal at last update level 1008 | # Ideally, we should validate the key removal operation, but the 1009 | # probabilty of removing another key, when a bid is accepted, should 1010 | # be sufficiently low 1011 | ########################################################################## 1012 | key_removed <- operations %>% 1013 | filter(., level == bm_last_level, parameterEntry == "retract_offer" | parameterEntry == "offer") %>% 1014 | nrow(.) > 0 1015 | 1016 | # If the key has beem removed within the time window... 1017 | offer_removed <- bm_last_action == "remove_key" & bm_last_action_date <= date_span[2] 1018 | if (offer_removed & !key_removed) { 1019 | x %<>% mutate(., 1020 | tokenID = token_id, 1021 | tokenAmount = 1, 1022 | xtzSent = xtzFee + token_amount / 1000000, 1023 | tokenReceiver = SenderAddress, 1024 | case = "OBJKT v2 offer purchase" 1025 | ) 1026 | } 1027 | else { 1028 | x %<>% mutate(., 1029 | xtzSent = xtzFee, 1030 | xtzReceived = 0, 1031 | case = "OBJKT v2 offer" 1032 | ) 1033 | } 1034 | } 1035 | 1036 | # OBJKT v2 bid 1037 | else if ("bid" %in% x$parameterEntry) { 1038 | x %<>% 1039 | filter(., 1040 | parameterEntry == "transfer", 1041 | tokenReceiver == "KT18p94vjkkHYY3nPmernmgVR7HdZFzE7NAk" 1042 | ) %>% 1043 | mutate(., case = "OBJKT v2 bid") 1044 | } 1045 | 1046 | 1047 | # OBJKT v2 fulfill offer (trade) 1048 | else if ( 1049 | ("fulfill_offer" %in% x$parameterEntry) & 1050 | (sum(wallets %in% x$initiatorAddress) > 0) 1051 | ) { 1052 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1053 | x %<>% 1054 | filter(., parameterEntry == "transfer") %>% 1055 | mutate(., 1056 | tokenAmount = ifelse(xtzCollect_bid != xtzReceived, 0, tokenAmount), 1057 | tokenSender = ifelse(xtzCollect_bid != xtzReceived, NA, token_sender), 1058 | case = ifelse( 1059 | xtzCollect_bid != xtzReceived, 1060 | "OBJKT v2 fulfill offer (sales/royalties)", 1061 | "OBJKT v2 fulfill offer (trade)" 1062 | ) 1063 | ) 1064 | } 1065 | 1066 | # OBJKT v2 fulfill offer (collect) 1067 | else if ( 1068 | ("fulfill_offer" %in% x$parameterEntry) & 1069 | (sum(wallets %in% x$initiatorAddress) == 0) 1070 | ) { 1071 | x %<>% quick_case(., entry="transfer", case="OBJKT v2 fulfill offer (collect)") 1072 | } 1073 | 1074 | # OBJKT v2 fulfill ask (trade) 1075 | else if ( 1076 | ("fulfill_ask" %in% x$parameterEntry) & 1077 | (sum(wallets %in% x$initiatorAddress) == 0) 1078 | ) { 1079 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1080 | x %<>% 1081 | filter(., parameterEntry == "transfer") %>% 1082 | mutate(., 1083 | tokenAmount = ifelse(xtzCollect != xtzReceived, 0, tokenAmount), 1084 | tokenSender = ifelse(xtzCollect != xtzReceived, NA, token_sender), 1085 | case = ifelse( 1086 | xtzCollect != xtzReceived, 1087 | "OBJKT v2 fulfill ask (sales/royalties)", 1088 | "OBJKT v2 fulfill ask (trade)" 1089 | ) 1090 | ) 1091 | } 1092 | 1093 | # OBJKT v2 fulfill ask (collect) 1094 | else if ( 1095 | ("fulfill_ask" %in% x$parameterEntry) & 1096 | (sum(wallets %in% x$initiatorAddress) > 0) 1097 | ) { 1098 | x %<>% quick_case(., entry="transfer", case="OBJKT v2 fulfill ask (collect)") 1099 | } 1100 | 1101 | # OBJKT retract offer 1102 | else if ("retract_offer" %in% x$parameterEntry) { 1103 | x %<>% 1104 | quick_case(., entry="retract_offer", case="OBJKT v2 retract offer") %>% 1105 | mutate(., xtzSent = xtzFee, xtzReceived = 0) 1106 | } 1107 | 1108 | # OBJKT v2 unwrap 1109 | else if ("unwrap" %in% x$parameterEntry) { 1110 | x %<>% quick_case(., entry="unwrap", case="OBJKT v2 unwrap") %>% 1111 | mutate(., 1112 | # tokenAmount = as.numeric(parameterValue), 1113 | # tokenID = "KT1TjnZYs5CGLbmV6yuW169P8Pnr9BiVwwjz_0", 1114 | # tokenSender = SenderAddress 1115 | tokenAmount = 0, 1116 | xtzSent = xtzFee, 1117 | xtzReceived = 0 1118 | ) 1119 | } 1120 | 1121 | # OBJKT v2 wrap 1122 | else if ("wrap" %in% x$parameterEntry) { 1123 | # z <- x %>% filter(., parameterEntry == "wrap") 1124 | x %<>% quick_case(., entry="wrap", case="OBJKT v2 wrap") %>% 1125 | mutate(., 1126 | # tokenAmount = as.numeric(z$parameterValue[[1]]$value), 1127 | # tokenID = "KT1TjnZYs5CGLbmV6yuW169P8Pnr9BiVwwjz_0", 1128 | # tokenSender = SenderAddress 1129 | tokenAmount = 0, 1130 | xtzSent = xtzFee, 1131 | xtzReceived = 0 1132 | ) 1133 | } 1134 | 1135 | # OBJKT v2 unidentified 1136 | else { 1137 | x <- y 1138 | } 1139 | } 1140 | 1141 | # C-Verso contracts 1142 | else if (sum(cverso_contracts %in% x$targetAddress) >0) { 1143 | 1144 | # C-Verso mint 1145 | if ("mint_token" %in% x$parameterEntry) { 1146 | x_hash <- tzkt_operations_hash(x$hash[1]) 1147 | token_id <- as.numeric(x_hash[2,]$storage$last_token_id) - 1 1148 | token_id <- paste0(x_hash[2,]$target$address, "_", token_id) 1149 | x$tokenID <- token_id 1150 | x %<>% 1151 | quick_case(., entry="mint", case="C-Verso mint") %>% 1152 | mutate(., tokenAmount = 1) 1153 | } 1154 | 1155 | # C-Verso unidentified 1156 | else { 1157 | x <- y 1158 | } 1159 | 1160 | } 1161 | 1162 | # typed contracts 1163 | else if (sum(typed_contracts %in% x$targetAddress) > 0) { 1164 | 1165 | # typed trade 1166 | if ( 1167 | ("collect" %in% x$parameterEntry) & 1168 | (sum(wallets %in% x$initiatorAddress) == 0) 1169 | ) { 1170 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1171 | x %<>% 1172 | top_n(., n=1, wt=id) %>% 1173 | mutate(., 1174 | tokenAmount = ifelse( 1175 | xtzCollect != xtzReceived, 1176 | 0, 1177 | as.numeric(list_check(parameterValue, "amount")) 1178 | ), 1179 | tokenSender = ifelse( 1180 | xtzCollect != xtzReceived, 1181 | NA, 1182 | token_sender 1183 | ), 1184 | case = ifelse( 1185 | xtzCollect != xtzReceived, 1186 | "typed collect (sales/royalties)", 1187 | "typed collect (trade)" 1188 | ) 1189 | ) 1190 | } 1191 | 1192 | # typed collect 1193 | else if ( 1194 | ("collect" %in% x$parameterEntry) & 1195 | (sum(wallets %in% x$initiatorAddress) > 0) 1196 | ) { 1197 | x %<>% quick_case(., case="typed collect", type=2) 1198 | } 1199 | 1200 | # typed unidentified 1201 | else { 1202 | x <- y 1203 | } 1204 | 1205 | } 1206 | 1207 | # 8scribo contracts 1208 | else if (sum(scribo_contracts %in% x$targetAddress) > 0) { 1209 | 1210 | # 8scribo trade 1211 | if ( 1212 | ("collect" %in% x$parameterEntry) & 1213 | (sum(wallets %in% x$initiatorAddress) == 0) 1214 | ) { 1215 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1216 | x %<>% 1217 | top_n(., n=1, wt=id) %>% 1218 | mutate(., 1219 | tokenAmount = ifelse( 1220 | xtzCollect != xtzReceived, 1221 | 0, 1222 | as.numeric(list_check(parameterValue, "amount")) 1223 | ), 1224 | tokenSender = ifelse( 1225 | xtzCollect != xtzReceived, 1226 | NA, 1227 | token_sender 1228 | ), 1229 | case = ifelse( 1230 | xtzCollect != xtzReceived, 1231 | "8scribo collect (sales/royalties)", 1232 | "8scribo collect (trade)" 1233 | ) 1234 | ) 1235 | } 1236 | 1237 | # 8scribo collect 1238 | else if ( 1239 | ("collect" %in% x$parameterEntry) & 1240 | (sum(wallets %in% x$initiatorAddress) > 0) 1241 | ) { 1242 | x %<>% quick_case(., case="8scribo collect", type=2) 1243 | } 1244 | 1245 | # 8scribo unidentified 1246 | else { 1247 | x <- y 1248 | } 1249 | 1250 | } 1251 | 1252 | # akaSwap contracts 1253 | else if (sum(aka_contracts %in% x$targetAddress) > 0) { 1254 | 1255 | # akaSwap mint 1256 | if ("mint" %in% x$parameterEntry) { 1257 | x %<>% 1258 | filter(., parameterEntry == "mint") %>% 1259 | mutate(., 1260 | tokenSender = NA, 1261 | tokenReceiver = initiatorAddress, 1262 | case = "akaSwap mint" 1263 | ) 1264 | } 1265 | 1266 | # akaSwap trade 1267 | else if ( 1268 | ("collect" %in% x$parameterEntry) & 1269 | (sum(wallets %in% x$initiatorAddress) == 0) 1270 | ) { 1271 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1272 | x %<>% 1273 | top_n(., n=1, wt=id) %>% 1274 | mutate(., 1275 | tokenAmount = ifelse( 1276 | xtzCollect != xtzReceived, 1277 | 0, 1278 | as.numeric(list_check(parameterValue, "amount")) 1279 | ), 1280 | tokenSender = ifelse( 1281 | xtzCollect != xtzReceived, 1282 | NA, 1283 | token_sender 1284 | ), 1285 | case = ifelse( 1286 | xtzCollect != xtzReceived, 1287 | "akaSwap collect (sales/royalties)", 1288 | "akaSwap collect (trade)" 1289 | ) 1290 | ) 1291 | } 1292 | 1293 | # akaSwap collect 1294 | else if ( 1295 | ("collect" %in% x$parameterEntry) & 1296 | (sum(wallets %in% x$initiatorAddress) > 0) 1297 | ) { 1298 | x %<>% quick_case(., case="akaSwap collect", type=2) 1299 | } 1300 | 1301 | # akaSwap collect bundle 1302 | else if ( 1303 | ("collect_bundle" %in% x$parameterEntry) & 1304 | (sum(wallets %in% x$initiatorAddress) > 0) 1305 | ) { 1306 | x %<>% quick_case(., case="akaSwap collect bundle", type=2) 1307 | x_params <- x$parameterValue[[1]][[1]][[1]] 1308 | x_n <- nrow(x_params) 1309 | y <- x 1310 | x <- x[0, ] 1311 | for (i in 1:x_n) { 1312 | x_i <- y 1313 | x_i$tokenReceiver <- x_params$to_[[i]] 1314 | x_i$tokenAmount <- as.numeric(x_params$amount[[i]]) 1315 | x_i$tokenID <- paste0(x_i$targetAddress, "_", x_params$token_id[[i]]) 1316 | x_i$xtzSent <- x_i$xtzSent / x_n 1317 | x %<>% bind_rows(., x_i) 1318 | } 1319 | } 1320 | 1321 | # akaSwap swap 1322 | else if ("swap" %in% x$parameterEntry) { 1323 | x %<>% quick_case(., entry="swap", case="akaSwap swap") 1324 | } 1325 | 1326 | # akaSwap cancel swap 1327 | else if ("cancel_swap" %in% x$parameterEntry) { 1328 | x %<>% quick_case(., entry="cancel_swap", case="akaSwap cancel swap") 1329 | } 1330 | 1331 | # akaSwap gachapon royalties 1332 | else if ( 1333 | ("default" %in% x$parameterEntry) & 1334 | (sum(wallets %in% x$initiatorAddress) == 0) 1335 | ) { 1336 | target_address <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1337 | x %<>% 1338 | filter(., targetAddress == target_address) %>% 1339 | mutate(., case = "akaSwap gachapon royalties") 1340 | } 1341 | 1342 | # akaSwap offer 1343 | else if ("make_offer" %in% x$parameterEntry) { 1344 | x %<>% mutate(., 1345 | xtzSent = xtzFee, 1346 | case = "akaSwap offer" 1347 | ) 1348 | } 1349 | 1350 | # akaSwap cancel offer 1351 | else if ("cancel_offer" %in% x$parameterEntry) { 1352 | x %<>% mutate(., 1353 | xtzReceived = 0, 1354 | case = "akaSwap cancel offer" 1355 | ) 1356 | } 1357 | 1358 | # akaSwap unidentified 1359 | else { 1360 | x <- y 1361 | } 1362 | } 1363 | 1364 | # emprops contracts 1365 | else if (sum(emprops_contracts %in% x$targetAddress) > 0) { 1366 | 1367 | # emprops mint 1368 | if ("mint_token" %in% x$parameterEntry) { 1369 | x_hash <- tzkt_operations_hash(hash=x$hash[1], quote=currency) 1370 | x_hash %<>% filter(., parameter$entrypoint == "mint") 1371 | token_id <- str_c(x_hash[1,]$target$address, "_", x_hash[1,]$diffs[[1]]$content$key[1]) 1372 | token_receiver <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1373 | x %<>% 1374 | filter(., parameterEntry == "mint") %>% 1375 | mutate(., 1376 | tokenID = token_id, 1377 | tokenAmount = 1, 1378 | tokenReceiver = token_receiver, 1379 | case = "emprops mint" 1380 | ) 1381 | } 1382 | 1383 | # emprops sale 1384 | if ( 1385 | ("create_sale" %in% x$parameterEntry) 1386 | ) { 1387 | x_hash <- tzkt_operations_hash(hash=x$hash[1], quote=currency) 1388 | contract_address <- x_hash[1,]$storage$token_contract_address 1389 | token_id <- max(x[1,]$parameterValue[[1]]$token_id, na.rm=TRUE) 1390 | token_receiver <- x[1,]$SenderAddress 1391 | token_sender <- x[nrow(x),]$targetAddress 1392 | x %<>% 1393 | top_n(., n=-1, wt=id) %>% 1394 | mutate(., 1395 | tokenID = paste0(contract_address, "_", token_id), 1396 | tokenAmount = 1, 1397 | tokenReceiver = token_receiver, 1398 | tokenSender = token_sender, 1399 | case = "emprops sale" 1400 | ) 1401 | } 1402 | 1403 | # emprops unidentified 1404 | else { 1405 | x <- y 1406 | } 1407 | } 1408 | 1409 | # 8bidou contracts 1410 | else if (sum(eightbidou_contracts %in% x$targetAddress) > 0) { 1411 | 1412 | # 8bidou buy 1413 | if ( 1414 | ("buy" %in% x$parameterEntry) & 1415 | (sum(wallets %in% x$initiatorAddress) > 0) 1416 | ) { 1417 | x %<>% quick_case(., entry="transfer", case="8bidou buy") 1418 | } 1419 | 1420 | # 8bidou sell 1421 | else if ( 1422 | ("buy" %in% x$parameterEntry) & 1423 | (sum(wallets %in% x$initiatorAddress) == 0) 1424 | ) { 1425 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1426 | x %<>% 1427 | filter(., parameterEntry == "transfer") %>% 1428 | mutate(., 1429 | tokenAmount = ifelse(xtzCollect != xtzReceived, 0, tokenAmount), 1430 | tokenSender = ifelse(xtzCollect != xtzReceived, NA, token_sender), 1431 | case = ifelse( 1432 | xtzCollect != xtzReceived, 1433 | "8bidou buy (sales/royalties)", 1434 | "8bidou buy (trade)" 1435 | ) 1436 | ) 1437 | } 1438 | 1439 | # 8bidou swap 1440 | else if ("swap" %in% x$parameterEntry) { 1441 | x %<>% quick_case(., entry="swap", case="8bidou swap") 1442 | } 1443 | 1444 | # 8bidou cancel swap 1445 | else if ("cancelswap" %in% x$parameterEntry) { 1446 | x %<>% quick_case(., entry="cancelswap", case="8bidou cancel swap") 1447 | } 1448 | 1449 | # 8bidou unidentified 1450 | else { 1451 | x <- y 1452 | } 1453 | } 1454 | 1455 | # Tezos Vending Machine contracts 1456 | else if (sum(vending_contracts %in% x$targetAddress) > 0) { 1457 | 1458 | # Vending machine collect 1459 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1460 | if ("collect_from_machine" %in% x$parameterEntry) { 1461 | x %<>% 1462 | filter(., parameterEntry == "transfer") %>% 1463 | mutate(., 1464 | tokenAmount = 1, 1465 | tokenSender = token_sender, 1466 | xtzSent = xtzSent / nrow(.), 1467 | case = "Tezos Vending machine collect" 1468 | ) 1469 | } 1470 | 1471 | # Vending machine unidentified 1472 | else { 1473 | x <- y 1474 | } 1475 | } 1476 | 1477 | # Endless Ways contracts 1478 | else if (sum(endless_ways_contracts %in% x$targetAddress) > 0) { 1479 | 1480 | # Endless Ways buy 1481 | if ( 1482 | ("mint_and_purchase" %in% x$parameterEntry) & 1483 | (sum(wallets %in% x$initiatorAddress) > 0) 1484 | ) { 1485 | x %<>% quick_case(., entry="mint_and_purchase", case="Endless ways mint") 1486 | } 1487 | 1488 | # Endless Ways unidentified 1489 | else { 1490 | x <- y 1491 | } 1492 | } 1493 | 1494 | # Mooncakes mint 1495 | else if ( 1496 | ("KT1WvV2rPBQUFUqtCWmnnj8JX2gkmDtMBzQi" %in% x$targetAddress) & 1497 | ("mint" %in% x$parameterEntry) 1498 | ) { 1499 | x %<>% quick_case(., entry="mint", case="mooncakes mint") 1500 | } 1501 | 1502 | # Tezzardz mint 1503 | else if ( 1504 | ("KT1LHHLso8zQWQWg1HUukajdxxbkGfNoHjh6" %in% x$targetAddress) & 1505 | ("mint" %in% x$parameterEntry) 1506 | ) { 1507 | tz <- as.numeric(x$parameterValue[[1]]) 1508 | x %<>% 1509 | filter(., 1510 | parameterEntry == "mint", 1511 | !row_number() == 1 1512 | ) %>% 1513 | mutate(., 1514 | xtzSent = xtzSent / tz, 1515 | tokenSender = targetAddress, 1516 | tokenReceiver = list_check(parameterValue, "address"), 1517 | case = "Tezzardz mint" 1518 | ) 1519 | } 1520 | 1521 | # Gogos mint 1522 | else if ( 1523 | ("KT1CExkW5WoKqoiv5An6uaZzN6i2Q3mxcqpW" %in% x$targetAddress) & 1524 | ("mint" %in% x$parameterEntry) 1525 | ) { 1526 | tz <- as.numeric(x$parameterValue[[1]]) 1527 | x %<>% 1528 | filter(., 1529 | parameterEntry == "mint", 1530 | !row_number() == 1 1531 | ) %>% 1532 | mutate(., 1533 | xtzSent = xtzSent / tz, 1534 | tokenSender = targetAddress, 1535 | tokenReceiver = list_check(parameterValue, "address"), 1536 | case = "Gogos mint" 1537 | ) 1538 | } 1539 | 1540 | # Neonz mint 1541 | else if ( 1542 | ("KT1QMAN7pWrR7fdiiMZ8mtVMMeFw2nADcVAH" %in% x$targetAddress) & 1543 | ("mint" %in% x$parameterEntry) 1544 | ) { 1545 | tz <- as.numeric(x$parameterValue[[1]]) 1546 | x %<>% 1547 | filter(., 1548 | parameterEntry == "mint", 1549 | !row_number() == 1 1550 | ) %>% 1551 | mutate(., 1552 | xtzSent = xtzSent / tz, 1553 | tokenSender = targetAddress, 1554 | tokenReceiver = list_check(parameterValue, "address"), 1555 | case = "Neonz mint" 1556 | ) 1557 | } 1558 | 1559 | # Ziggurats mint 1560 | else if ( 1561 | ("KT1NC4pPLbhcw5JRq89NnHgwXg9uGztSZm1k" %in% x$targetAddress) & 1562 | ("mint" %in% x$parameterEntry) 1563 | ) { 1564 | tz <- as.numeric(x$parameterValue[[1]]) 1565 | x %<>% 1566 | filter(., 1567 | parameterEntry == "mint", 1568 | !row_number() == 1 1569 | ) %>% 1570 | mutate(., 1571 | xtzSent = xtzSent / tz, 1572 | tokenSender = targetAddress, 1573 | tokenReceiver = list_check(parameterValue, "address"), 1574 | case = "Ziggurats mint" 1575 | ) 1576 | } 1577 | 1578 | # Hash Three Points mint 1579 | else if ( 1580 | ("KT1Fxz4V3LaUcVFpvF8pAAx8G3Z4H7p7hhDg" %in% x$targetAddress) & 1581 | ("mint" %in% x$parameterEntry) 1582 | ) { 1583 | x %<>% quick_case(., entry="mint", case="H3P mint") 1584 | token_id <- as.numeric(tzkt_operations_hash(x$hash[1])$storage$next_id) - 1 1585 | token_id <- paste0(x$targetAddress[1], "_", token_id) 1586 | x$tokenID <- token_id 1587 | } 1588 | 1589 | # Randomly Common Skeles mint 1590 | else if ( 1591 | ("KT1AvxTNETj3U4b3wKYxkX6CKya1EgLZezv8" %in% x$targetAddress) & 1592 | ("buy" %in% x$parameterEntry) 1593 | ){ 1594 | x %<>% quick_case(., entry="buy", case="RCS mint") 1595 | } 1596 | 1597 | # Pixel Potus contracts 1598 | else if ("KT1WGDVRnff4rmGzJUbdCRAJBmYt12BrPzdD" %in% x$targetAddress) { 1599 | 1600 | # Pixel Potus claim 1601 | if ("claim" %in% x$parameterEntry) { 1602 | claim_paid <- sum(filter(x, parameterEntry == "claim_paid")$xtzAmount) 1603 | x %<>% 1604 | quick_case(., entry="claim", case="Pixel Potus claim") %>% 1605 | mutate(., xtzSent = claim_paid + xtzFee) 1606 | } 1607 | 1608 | # Pixel Potus trade 1609 | else if ("take_trade" %in% x$parameterEntry) { 1610 | x %<>% quick_case(., entry="transfer", case="Pixel Potus trade") 1611 | } 1612 | 1613 | # Pixel Potus unidentified 1614 | else { 1615 | x <- y 1616 | } 1617 | } 1618 | 1619 | # DKRBT homebase 1620 | else if ("KT1DNHADdFxHM6mRKTgyJmchW5ELxcoW1aSh" %in% x$targetAddress) { 1621 | 1622 | # DKRBT freeze (staking) 1623 | if ("freeze" %in% x$parameterEntry) { 1624 | x %<>% quick_case(., case="DKRBT freeze", type=2) 1625 | x %<>% mutate(., tokenAmount = 0) 1626 | } 1627 | 1628 | # DKRBT unfreeze (unstake) 1629 | ############################################################################ 1630 | # NOTE: Unfreeze classification does not account for DKRBT earned through 1631 | # staking. That is, output will not properly classify earned tokens as 1632 | # income, but instead will assume that the tokens were acquired at a cost 1633 | # basis of 0 when the tokens are sold. 1634 | ############################################################################ 1635 | else if ("unfreeze" %in% x$parameterEntry) { 1636 | x %<>% quick_case(., case="DKRBT unfreeze", type=2) 1637 | x %<>% mutate(., tokenAmount = 0) 1638 | } 1639 | 1640 | # DKRBT unidentified 1641 | else { 1642 | x <- y 1643 | } 1644 | } 1645 | 1646 | # fxhash contracts 1647 | else if (sum(fx_contracts %in% x$targetAddress) > 0) { 1648 | 1649 | # fxhash issue mint 1650 | if ("mint_issuer" %in% x$parameterEntry) { 1651 | x %<>% quick_case(., entry="mint_issuer", case="fxhash mint issue") 1652 | } 1653 | 1654 | # fxhash mint 1655 | else if ("mint" %in% x$parameterEntry) { 1656 | x %<>% 1657 | filter(., parameterEntry == "mint", !row_number() == 1) %>% 1658 | mutate(., 1659 | case = "fxhash mint", 1660 | tokenAmount = 1 1661 | ) 1662 | 1663 | if (x$tokenSender %in% wallets) { 1664 | x %<>% mutate(., 1665 | case = "fxhash self-mint", 1666 | tokenReceiver = tokenSender, 1667 | tokenSender = NA 1668 | ) 1669 | } 1670 | 1671 | } 1672 | 1673 | # fxhash offer (list) 1674 | else if ("offer" %in% x$parameterEntry) { 1675 | x %<>% quick_case(., entry="offer", case="fxhash offer") 1676 | x <- x[1, ] 1677 | } 1678 | 1679 | # fxhash cancel offer 1680 | else if ("cancel_offer" %in% x$parameterEntry) { 1681 | x %<>% quick_case(., entry="cancel_offer", case="fxhash cancel offer") 1682 | x <- x[1, ] 1683 | } 1684 | 1685 | # fxhash trade 1686 | else if ( 1687 | ("collect" %in% x$parameterEntry) & 1688 | (sum(wallets %in% x$initiatorAddress) == 0) 1689 | ) { 1690 | x %<>% 1691 | filter(., parameterEntry == "transfer") %>% 1692 | mutate(., 1693 | tokenAmount = ifelse( 1694 | xtzCollect != xtzReceived, 1695 | 0, 1696 | as.numeric(list_check(parameterValue, "amount")) 1697 | ), 1698 | tokenSender = ifelse( 1699 | xtzCollect != xtzReceived, 1700 | NA, 1701 | wallets[1] 1702 | ), 1703 | case = ifelse( 1704 | xtzCollect != xtzReceived, 1705 | "fxhash collect (sales/royalties)", 1706 | "fxhash collect (trade)" 1707 | ) 1708 | ) 1709 | } 1710 | 1711 | # fxhash collect 1712 | else if ( 1713 | ("collect" %in% x$parameterEntry) & 1714 | (sum(wallets %in% x$initiatorAddress) > 0) 1715 | ) { 1716 | x %<>% quick_case(., entry="transfer", case="fxhash collect") 1717 | } 1718 | 1719 | # fxhash update profile 1720 | else if ("update_profile" %in% x$parameterEntry) { 1721 | x %<>% quick_case(., entry="update_profile", case="fxhash update profile") 1722 | } 1723 | 1724 | # fxhash unidentified 1725 | else { 1726 | x <- y 1727 | } 1728 | } 1729 | 1730 | # fxhash v2 contracts 1731 | else if (sum(fx_v2_contracts %in% x$targetAddress) > 0) { 1732 | 1733 | # fxhash v2 mint 1734 | if ("mint" %in% x$parameterEntry) { 1735 | x %<>% 1736 | filter(., parameterEntry == "mint", !row_number() == 1) %>% 1737 | mutate(., 1738 | case = "fxhash v2 mint", 1739 | tokenAmount = 1 1740 | ) 1741 | 1742 | if (x$tokenSender %in% wallets) { 1743 | x %<>% mutate(., 1744 | case = "fxhash v2 self-mint", 1745 | tokenReceiver = tokenSender, 1746 | tokenSender = NA 1747 | ) 1748 | } 1749 | 1750 | } 1751 | 1752 | # fxhash v2 listing 1753 | else if ("listing" %in% x$parameterEntry) { 1754 | x %<>% quick_case(., entry="listing", case="fxhash v2 listing") %>% 1755 | filter(., row_number() == 1) 1756 | } 1757 | 1758 | # fxhash v2 trade 1759 | else if ( 1760 | ("listing_accept" %in% x$parameterEntry) & 1761 | (sum(wallets %in% x$initiatorAddress) == 0) 1762 | ) { 1763 | x %<>% 1764 | filter(., parameterEntry == "transfer") %>% 1765 | mutate(., 1766 | tokenAmount = ifelse( 1767 | xtzCollect != xtzReceived, 1768 | 0, 1769 | as.numeric(list_check(parameterValue, "amount")) 1770 | ), 1771 | tokenSender = ifelse( 1772 | xtzCollect != xtzReceived, 1773 | NA, 1774 | wallets[1] 1775 | ), 1776 | case = ifelse( 1777 | xtzCollect != xtzReceived, 1778 | "fxhash v2 collect (sales/royalties)", 1779 | "fxhash v2 collect (trade)" 1780 | ) 1781 | ) 1782 | 1783 | } 1784 | # fxhash cancel offer 1785 | else if ("cancel_offer" %in% x$parameterEntry) { 1786 | x %<>% quick_case(., entry="cancel_offer", case="fxhash cancel offer") 1787 | x <- x[1, ] 1788 | } 1789 | 1790 | # fxhash v2 collect 1791 | else if ( 1792 | ("listing_accept" %in% x$parameterEntry) & 1793 | (sum(wallets %in% x$initiatorAddress) > 0) 1794 | ) { 1795 | x %<>% quick_case(., entry="transfer", case="fxhash v2 collect") 1796 | } 1797 | 1798 | # fxhash v2 accept offer 1799 | else if ( 1800 | ("offer_accept" %in% x$parameterEntry) & 1801 | (sum(wallets %in% x$initiatorAddress) > 0) 1802 | ) { 1803 | x %<>% 1804 | filter(., parameterEntry == "transfer") %>% 1805 | top_n(., n=-1, wt=id) %>% 1806 | mutate(., 1807 | tokenAmount = ifelse( 1808 | xtzCollect_bid != xtzReceived, 1809 | 0, 1810 | as.numeric(list_check(parameterValue, "amount")) 1811 | ), 1812 | tokenSender = ifelse( 1813 | xtzCollect_bid != xtzReceived, 1814 | NA, 1815 | wallets[1] 1816 | ), 1817 | case = ifelse( 1818 | xtzCollect_bid != xtzReceived, 1819 | "fxhash v2 accept offer (sales/royalties)", 1820 | "fxhash v2 accept offer (trade)" 1821 | ) 1822 | ) 1823 | } 1824 | 1825 | # fxhash v2 accept collection offer 1826 | else if ( 1827 | ("collection_offer_accept" %in% x$parameterEntry) & 1828 | (sum(wallets %in% x$initiatorAddress) > 0) 1829 | ) { 1830 | z <- x %>% filter(., parameterEntry == "update_operators") 1831 | contract_address <- z[1, ]$targetAddress 1832 | token_id <- z[1, ]$parameterValue[[1]]$add_operator$token_id 1833 | x %<>% 1834 | top_n(., n=-1, wt=id) %>% 1835 | mutate(., 1836 | tokenID = paste0(contract_address, "_", token_id), 1837 | tokenSender = SenderAddress, 1838 | tokenAmount = 1, 1839 | case = ifelse( 1840 | xtzCollect_bid != xtzReceived, 1841 | "fxhash v2 accept collection offer (sales/royalties)", 1842 | "fxhash v2 accept collection offer (trade)" 1843 | ) 1844 | ) 1845 | } 1846 | 1847 | # fxhash v2 collection offer cancel 1848 | else if ("collection_offer_cancel" %in% x$parameterEntry) { 1849 | x %<>% quick_case(., 1850 | entry="collection_offer_cancel", case="fxhash cancel collection offer" 1851 | ) %>% 1852 | mutate(., xtzReceived = 0) 1853 | x <- x[1, ] 1854 | } 1855 | 1856 | # fxhash v2 collection offer 1857 | else if ("collection_offer" %in% x$parameterEntry) { 1858 | x %<>% quick_case(., 1859 | entry="collection_offer", case="fxhash collection offer" 1860 | ) 1861 | x <- x[1, ] 1862 | } 1863 | 1864 | # fxhash v2 offer 1865 | else if ("offer" %in% x$parameterEntry) { 1866 | x %<>% mutate(., 1867 | xtzSent = xtzFee, 1868 | case="fxhash v2 offer" 1869 | ) 1870 | } 1871 | 1872 | # fxhash v2 cancel offer 1873 | else if ("offer_cancel" %in% x$parameterEntry) { 1874 | x %<>% 1875 | top_n(., n=-1) %>% 1876 | mutate(., 1877 | xtzReceived = 0, 1878 | case="fxhash v2 cancel offer" 1879 | ) 1880 | } 1881 | 1882 | # fxhash v2 listing cancel 1883 | else if ("listing_cancel" %in% x$parameterEntry) { 1884 | x %<>% quick_case(., entry="listing_cancel", case="fxhash v2 listing cancel") 1885 | } 1886 | 1887 | # fxhash v2 unidentified 1888 | else { 1889 | x <- y 1890 | } 1891 | } 1892 | 1893 | # Versum contracts 1894 | else if (sum(versum_contracts %in% x$targetAddress) > 0) { 1895 | 1896 | # Versum make offer 1897 | if ("make_offer" %in% x$parameterEntry) { 1898 | x_hash <- tzkt_operations_hash(hash=x$hash[1], quote=currency) 1899 | token_id <- str_c(x_hash[1,]$parameter$value$token$address, "_", x_hash[1,]$parameter$value$token$nat) 1900 | token_amount <- as.numeric(x_hash[1,]$parameter$value$token_amount) 1901 | 1902 | bm_id <- x_hash[1,]$diffs[[1]]$bigmap 1903 | bm_key <- x_hash[1,]$diffs[[1]]$content$key 1904 | bm_updates <- tzkt_bigmap_updates(bm_id, bm_key) 1905 | 1906 | bm_last_update <- bm_updates[nrow(bm_updates),] 1907 | bm_last_action <- bm_last_update$action 1908 | bm_last_action_date <- as_datetime(bm_last_update$timestamp) 1909 | bm_last_level <- bm_last_update$level 1910 | 1911 | ########################################################################## 1912 | # Check for key removal at last update level 1913 | # Ideally, we should validate the key removal operation, but the 1914 | # probabilty of removing another key, when a bid is accepted, should 1915 | # be sufficiently low 1916 | ########################################################################## 1917 | key_removed <- operations %>% 1918 | filter(., level == bm_last_level, parameterEntry == "make_offer" | parameterEntry == "cancel_offer") %>% 1919 | nrow(.) > 0 1920 | 1921 | # If the key has beem removed within the time window... 1922 | offer_removed <- bm_last_action == "remove_key" & bm_last_action_date <= date_span[2] 1923 | if (offer_removed & !key_removed) { 1924 | x %<>% mutate(., 1925 | tokenID = token_id, 1926 | tokenAmount = token_amount, 1927 | tokenReceiver = SenderAddress, 1928 | case = "Versum offer purchase" 1929 | ) 1930 | } 1931 | else { 1932 | x %<>% mutate(., 1933 | xtzSent = xtzFee, 1934 | xtzReceived = 0, 1935 | case = "Versum make offer" 1936 | ) 1937 | } 1938 | } 1939 | 1940 | # Versum accept offer (sell) 1941 | else if ( 1942 | ("accept_offer" %in% x$parameterEntry) & 1943 | (sum(wallets %in% x$initiatorAddress) > 0) 1944 | ) { 1945 | xtzCollect <- sort(x$xtzAmount, decreasing=TRUE)[3] 1946 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1947 | x %<>% 1948 | filter(., parameterEntry == "transfer") %>% 1949 | mutate(., 1950 | tokenAmount = ifelse(xtzCollect != xtzReceived, 0, tokenAmount), 1951 | tokenSender = ifelse(xtzCollect != xtzReceived, NA, token_sender), 1952 | case = ifelse( 1953 | xtzCollect != xtzReceived, 1954 | "Versum accept offer (sales/royalties)", 1955 | "Versum accept offer (trade)" 1956 | ) 1957 | ) 1958 | } 1959 | 1960 | # Versum bid 1961 | else if ("bid" %in% x$parameterEntry) { 1962 | x %<>% mutate(., 1963 | xtzSent = xtzFee, 1964 | xtzReceived = 0, 1965 | case = "Versum bid" 1966 | ) 1967 | } 1968 | 1969 | 1970 | # Versum cancel offer 1971 | else if ("cancel_offer" %in% x$parameterEntry) { 1972 | x %<>% mutate(., 1973 | xtzSent = xtzFee, 1974 | xtzReceived = 0, 1975 | case = "Versum cancel offer" 1976 | ) 1977 | } 1978 | 1979 | # Versum collect (buy) 1980 | else if ("collect_swap" %in% x$parameterEntry & (sum(wallets %in% x$initiatorAddress) > 0)) { 1981 | x %<>% quick_case(., entry="transfer", case="Versum collect (buy)") 1982 | } 1983 | 1984 | # Versum collect (sell) 1985 | else if ( 1986 | ("collect_swap" %in% x$parameterEntry) & 1987 | (sum(wallets %in% x$initiatorAddress) == 0) 1988 | ) { 1989 | xtzCollect <- sort(x$xtzAmount, decreasing=TRUE)[4] 1990 | n_collect <- sum(x$parameterEntry == "collect_swap", na.rm=TRUE) 1991 | if (n_collect == 1) { 1992 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 1993 | x %<>% 1994 | filter(., parameterEntry == "transfer") %>% 1995 | mutate(., 1996 | tokenAmount = ifelse(xtzCollect != xtzReceived, 0, tokenAmount), 1997 | tokenSender = ifelse(xtzCollect != xtzReceived, NA, token_sender), 1998 | case = ifelse( 1999 | xtzCollect != xtzReceived, 2000 | "Versum collect (sales/royalties)", 2001 | "Versum collect (trade)" 2002 | ) 2003 | ) 2004 | } 2005 | else { 2006 | x <- y 2007 | } 2008 | } 2009 | 2010 | # Versum claim verification 2011 | else if ("claim_verification" %in% x$parameterEntry) { 2012 | x %<>% quick_case(., entry="claim_verification", case="Versum claim verification") 2013 | } 2014 | 2015 | # Versum claim Materia 2016 | else if ("claim_materia" %in% x$parameterEntry) { 2017 | x %<>% quick_case(., entry="transfer", case="Versum claim Materia") 2018 | } 2019 | 2020 | # Versum swap 2021 | else if ("create_swap" %in% x$parameterEntry) { 2022 | x %<>% quick_case(., entry="create_swap", case="Versum swap") 2023 | } 2024 | 2025 | # Versum swap 2026 | else if ("cancel_swap" %in% x$parameterEntry) { 2027 | x %<>% quick_case(., entry="cancel_swap", case="Versum cancel swap") 2028 | } 2029 | 2030 | # Versum unidentified 2031 | else { 2032 | x <- y 2033 | } 2034 | 2035 | } 2036 | 2037 | # Minterpop contracts 2038 | else if (sum(minterpop_contracts %in% x$targetAddress) > 0) { 2039 | 2040 | # Minterpop buy 2041 | if ("buy" %in% x$parameterEntry) { 2042 | x %<>% quick_case(., entry="transfer", case="Minterpop buy") 2043 | } 2044 | 2045 | # Minterpop unidentified 2046 | else { 2047 | x <- y 2048 | } 2049 | 2050 | } 2051 | 2052 | # Emergent Properties contracts 2053 | else if (sum(c("KT1AML4jv2jMLKESGTApRZVoga5V5cSAMD2E") %in% x$targetAddress) > 0) { 2054 | 2055 | # Emergent Properties trade 2056 | if ( 2057 | ("create_sale" %in% x$parameterEntry) & 2058 | (sum(wallets %in% x$initiatorAddress) == 0) 2059 | ) { 2060 | n_collect <- sum(x$parameterEntry == "create_sale", na.rm=TRUE) 2061 | if (n_collect == 1) { 2062 | token_sender <- x$targetAddress[which(x$targetAddress %in% wallets)][1] 2063 | x %<>% 2064 | filter(., 2065 | parameterEntry == "create_sale", 2066 | SenderAddress == "KT1AML4jv2jMLKESGTApRZVoga5V5cSAMD2E" 2067 | ) %>% 2068 | mutate(., 2069 | tokenAmount = ifelse(xtzCollect != xtzReceived, 0, tokenAmount), 2070 | tokenSender = ifelse(xtzCollect != xtzReceived, NA, token_sender), 2071 | case = ifelse( 2072 | xtzCollect != xtzReceived, 2073 | "Emergent Properties collect (sales/royalties)", 2074 | "Emergent Properties collect (trade)" 2075 | ), 2076 | tokenID = str_c(targetAddress, "_", parameterValue[[1]]$token_id) 2077 | ) 2078 | } 2079 | else { 2080 | x <- y 2081 | } 2082 | } 2083 | 2084 | # Emergent Properties collect 2085 | else if ( 2086 | ("create_sale" %in% x$parameterEntry) & 2087 | (sum(wallets %in% x$initiatorAddress) > 0) 2088 | ) { 2089 | x %<>% 2090 | quick_case(., entry="create_sale", case="Emergent Properties collect") %>% 2091 | filter(., SenderAddress == "KT1AML4jv2jMLKESGTApRZVoga5V5cSAMD2E") %>% 2092 | mutate(., 2093 | tokenAmount = 1, 2094 | tokenReceiver = initiatorAddress, 2095 | tokenID = str_c(targetAddress, "_", parameterValue[[1]]$token_id) 2096 | ) 2097 | } 2098 | 2099 | # Emergent Properties list 2100 | else if ("list_token" %in% x$parameterEntry) { 2101 | x %<>% quick_case(., type=2, case="Emergent Properties list") 2102 | } 2103 | 2104 | # Emergent Properties unlist 2105 | else if ("unlist_token" %in% x$parameterEntry) { 2106 | x %<>% quick_case(., type=2, case="Emergent Properties unlist") 2107 | } 2108 | 2109 | # Emergent Properties undefined 2110 | else { 2111 | x <- y 2112 | } 2113 | 2114 | } 2115 | 2116 | # NFTbutton contracts 2117 | else if (sum(nftbutton_contracts %in% x$targetAddress) > 0) { 2118 | 2119 | # NFTbutton bid 2120 | if ("bid" %in% x$parameterEntry) { 2121 | x %<>% quick_case(., entry="transfer", case="NFTbutton bid") 2122 | } 2123 | 2124 | # NFTbutton mint 2125 | else if ("mint" %in% x$parameterEntry) { 2126 | x %<>% mutate(., case = "NFTbutton mint") 2127 | } 2128 | 2129 | # NFTbutton resolve 2130 | else if ("resolve" %in% x$parameterEntry) { 2131 | x %<>% quick_case(., entry="transfer", case="NFTbutton resolve") 2132 | } 2133 | 2134 | # NFTbutton unidentified 2135 | else { 2136 | x <- y 2137 | } 2138 | 2139 | } 2140 | 2141 | # NFTbutton old contract 2142 | else if (sum("KT1Tde5fNg9AZqyW8zjPxfAXhAveSasnp2Dq" %in% x$targetAddress) > 0) { 2143 | 2144 | # NFTbutton old collect 2145 | if ("collect" %in% x$parameterEntry) { 2146 | x %<>% quick_case(., entry="transfer", case="NFTbutton old collect") 2147 | } 2148 | 2149 | # NFTbutton old unidentified 2150 | else { 2151 | x <- y 2152 | } 2153 | 2154 | } 2155 | 2156 | # Tezos Mandala contracts 2157 | else if (sum("KT1DKBvxiwDR7qazNuuxCxY2AaXnoytmDE7H" %in% x$targetAddress) > 0) { 2158 | 2159 | # Tezos Mandala mint 2160 | if ("mint" %in% x$parameterEntry) { 2161 | x %<>% quick_case(., entry="mint", case="Tezos Mandala Mint") 2162 | } 2163 | 2164 | # Tezos Mandala wrap 2165 | else if ("wrap" %in% x$parameterEntry) { 2166 | x %<>% 2167 | filter(., parameterEntry == "mint") %>% 2168 | mutate(., 2169 | case = "Tezos Mandala Wrap" 2170 | ) 2171 | } 2172 | 2173 | # Tezos Mandala unidentified 2174 | else { 2175 | x <- y 2176 | } 2177 | 2178 | } 2179 | 2180 | # Editart mint ccontract 2181 | else if (sum("KT1D7Ufx21sz9yDyP4Rs1WBCur9XhaZ9JwNE" %in% x$targetAddress) > 0) { 2182 | if ("mint" %in% x$parameterEntry) { 2183 | x %<>% quick_case(., entry="mint", case="Editart mint") 2184 | } 2185 | else { 2186 | x <- y 2187 | } 2188 | } 2189 | 2190 | # Knights of Tezonia contracts 2191 | else if (sum("KT1VuqyJmkcSk2m5L9gEgAs69t6CCDDxLtiz" %in% x$targetAddress) > 0) { 2192 | 2193 | # Tezonia collect 2194 | if ("collect" %in% x$parameterEntry) { 2195 | x %<>% quick_case(., entry="mint", case="Knights of Tezonia mint") 2196 | } 2197 | 2198 | # Tezonia unidentified 2199 | else { 2200 | x <- y 2201 | } 2202 | 2203 | } 2204 | 2205 | # Rarible contracts 2206 | else if (sum(rari_contracts %in% x$targetAddress) > 0) { 2207 | 2208 | # Rarible collect 2209 | if ( 2210 | ("match_orders" %in% x$parameterEntry) & 2211 | (sum(wallets %in% x$initiatorAddress) > 0) 2212 | ) { 2213 | x %<>% quick_case(., entry="transfer", case="Rarible collect") 2214 | } 2215 | 2216 | # Rarible trade 2217 | else if ( 2218 | ("match_orders" %in% x$parameterEntry) & 2219 | (sum(wallets %in% x$initiatorAddress) == 0) 2220 | ) { 2221 | xtzCollect <- sort(x$xtzAmount, decreasing=TRUE)[5] 2222 | x %<>% 2223 | quick_case(., entry="transfer", case="Rarible trade") %>% 2224 | mutate(., 2225 | tokenAmount = ifelse(xtzCollect != xtzReceived, 0, tokenAmount), 2226 | tokenSender = ifelse(xtzCollect != xtzReceived, NA, token_sender), 2227 | case != ifelse( 2228 | xtzCollect > xtzReceived, 2229 | "Rarible collect (sales/royalties)", 2230 | "Rarible collect (trade)" 2231 | ) 2232 | ) 2233 | } 2234 | 2235 | # Rarible mint 2236 | else if ("mint" %in% x$parameterEntry) { 2237 | x %<>% mutate(., case = "Rarible mint") 2238 | } 2239 | 2240 | # Rarible update operators 2241 | else if ("update_operators_for_all" %in% x$parameterEntry) { 2242 | x %<>% quick_case(., entry="update_operators_for_all", case="Rarible update operators") 2243 | } 2244 | 2245 | # Rarible cancel 2246 | else if ("cancel" %in% x$parameterEntry) { 2247 | x %<>% quick_case(., entry="cancel", case="Rarible cancel") 2248 | } 2249 | 2250 | # Rarible cancel 2251 | else if ("burn" %in% x$parameterEntry) { 2252 | x %<>% quick_case(., entry="burn", case="Token transfer") 2253 | x %<>% mutate(., 2254 | tokenAmount = 1, 2255 | tokenSender = SenderAddress, 2256 | tokenReceiver = "", 2257 | tokenID = str_c(targetAddress, parameterValue[[1]]$itokenid, sep="_") 2258 | ) 2259 | } 2260 | 2261 | # Rarible unidentified 2262 | else { 2263 | x <- y 2264 | } 2265 | } 2266 | 2267 | # Goren tokens 2268 | else if ("KT1JBThDEqyqrEHimhxoUBCSnsKAqFcuHMkP" %in% x$targetAddress) { 2269 | 2270 | # Goren mint 2271 | if ("mint" %in% x$parameterEntry) { 2272 | x %<>% 2273 | filter(., parameterEntry == "mint") %>% 2274 | mutate(., 2275 | tokenReceiver = SenderAddress, 2276 | tokenAmount = 1, 2277 | case = "Goren mint" 2278 | ) 2279 | } 2280 | 2281 | # Goren update 2282 | else if ("update_operators" %in% x$parameterEntry) { 2283 | x %<>% quick_case(., entry="update_operators", case="Goren update") 2284 | } 2285 | 2286 | # Goren sell 2287 | else if ("sell" %in% x$parameterEntry) { 2288 | x %<>% 2289 | filter(., parameterEntry == "transfer") %>% 2290 | mutate(., case = "Goren sell") 2291 | } 2292 | 2293 | # Goren unidentified 2294 | else { 2295 | x <- y 2296 | } 2297 | } 2298 | 2299 | # WRAP tokens 2300 | else if (sum(wrap_contracts %in% x$targetAddress) > 0) { 2301 | 2302 | # WRAP mint 2303 | if ("minter" %in% x$parameterEntry) { 2304 | x %<>% 2305 | filter(., parameterEntry == "mint_tokens") %>% 2306 | mutate(., 2307 | tokenReceiver = initiatorAddress, 2308 | case = "WRAP mint" 2309 | ) 2310 | parameter_value <- filter(x[1, ]$parameterValue[[1]], owner %in% wallets) 2311 | token_id <- str_c(x[1,]$targetAddress, "_", parameter_value[1, ]$token_id) 2312 | token_amount <- parameter_value[1, ]$amount 2313 | x %<>% 2314 | mutate(., 2315 | tokenID = token_id, 2316 | tokenAmount = as.numeric(token_amount) 2317 | ) 2318 | } 2319 | 2320 | # WRAP unwrap 2321 | else if ("unwrap_erc20" %in% x$parameterEntry) { 2322 | x %<>% 2323 | filter(., parameterEntry == "burn_tokens") %>% 2324 | mutate(., 2325 | tokenSender = initiatorAddress, 2326 | case = "WRAP unwrap" 2327 | ) 2328 | parameter_value <- filter(x[1, ]$parameterValue[[1]], owner %in% wallets) 2329 | token_id <- str_c(x[1,]$targetAddress, "_", parameter_value[1, ]$token_id) 2330 | token_amount <- parameter_value[1, ]$amount 2331 | x %<>% 2332 | mutate(., 2333 | tokenID = token_id, 2334 | tokenAmount = as.numeric(token_amount) 2335 | ) 2336 | } 2337 | 2338 | # WRAP unidentified 2339 | else { 2340 | x <- y 2341 | } 2342 | } 2343 | 2344 | # WTZ swap 2345 | else if ("KT1Pyd1r9F4nMaHy8pPZxPSq6VCn9hVbVrf4" %in% x$targetAddress) { 2346 | 2347 | # WTZ unwrap 2348 | if ("unwrap" %in% x$parameterEntry) { 2349 | x %<>% 2350 | filter(., parameterEntry == "unwrap") %>% 2351 | mutate(., 2352 | tokenSender = SenderAddress, 2353 | tokenID = "KT1Pyd1r9F4nMaHy8pPZxPSq6VCn9hVbVrf4_0", 2354 | tokenAmount = as.numeric(list_check(parameterValue, "nat")), 2355 | case = "WTZ unwrap" 2356 | ) 2357 | } 2358 | 2359 | # WTZ wrap 2360 | else if ("wrap" %in% x$parameterEntry) { 2361 | z <- x %>% filter(., parameterEntry == "mint") 2362 | x %<>% 2363 | filter(., parameterEntry == "wrap") %>% 2364 | mutate(., 2365 | tokenReceiver = SenderAddress, 2366 | tokenID = "KT1Pyd1r9F4nMaHy8pPZxPSq6VCn9hVbVrf4_0", 2367 | tokenAmount = as.numeric(z$parameterValue[[1]]$nat_1), 2368 | case = "WTZ wrap" 2369 | ) 2370 | } 2371 | 2372 | # WTZ unidentified 2373 | else { 2374 | x <- y 2375 | } 2376 | 2377 | } 2378 | 2379 | # Gogos contract 2380 | else if (sum("KT1Xf44LpwrA7oBcB3VwWTtUBP1eNRaNnWeh" %in% x$targetAddress) > 0) { 2381 | 2382 | # Gogos consume 2383 | if ("consume" %in% x$parameterEntry) { 2384 | x %<>% quick_case(., case="Gogos consume", type=2) 2385 | } 2386 | 2387 | # Gogos return 2388 | else if ("return_item" %in% x$parameterEntry) { 2389 | x %<>% quick_case(., case="Gogos return", type=2) 2390 | } 2391 | 2392 | # Gogos unidentified 2393 | else { 2394 | x <- y 2395 | } 2396 | 2397 | } 2398 | 2399 | # Kolibri oven 2400 | else if (sum(kolibri_contracts %in% x$targetAddress) > 0) { 2401 | 2402 | # Kolibri oven deposit 2403 | if ("deposit" %in% x$parameterEntry) { 2404 | x %<>% 2405 | top_n(., n=-1, wt=id) %>% 2406 | mutate(., 2407 | xtzSent = xtzFee, 2408 | xtzReceived = 0, 2409 | case = "Kolibri oven deposit" 2410 | ) 2411 | } 2412 | 2413 | # Kolibri oven borrow 2414 | else if ("borrow" %in% x$parameterEntry) { 2415 | x %<>% 2416 | filter(., parameterEntry == "mint") %>% 2417 | mutate(., 2418 | costBasis = tokenAmount / 1000000000000000000, 2419 | case = "Kolibri oven borrow" 2420 | ) 2421 | } 2422 | 2423 | # Kolibri oven repay 2424 | else if ("repay" %in% x$parameterEntry) { 2425 | x %<>% 2426 | top_n(., n=-1, wt=id) %>% 2427 | mutate(., 2428 | tokenAmount = as.numeric(parameterValue), 2429 | tokenReceiver = SenderAddress, 2430 | tokenID = "KT1K9gCRgaLRFKTErYt1wVxA3Frb9FjasjTV_NA", 2431 | tokenProceeds = tokenAmount / 1000000000000000000, 2432 | case = "Kolibri oven repay" 2433 | ) 2434 | } 2435 | 2436 | # Kolibri oven withdraw 2437 | else if ("withdraw" %in% x$parameterEntry) { 2438 | x %<>% 2439 | top_n(., n=-1, wt=id) %>% 2440 | mutate(., 2441 | xtzSent = xtzFee, 2442 | xtzReceived = 0, 2443 | case = "Kolibri oven withdraw" 2444 | ) 2445 | } 2446 | 2447 | # Kolibri make oven 2448 | else if ("makeOven" %in% x$parameterEntry) { 2449 | x %<>% quick_case(., entry="makeOven", case="Kolibri make oven") 2450 | } 2451 | 2452 | # Kolibri oven unidentified 2453 | else { 2454 | x <- y 2455 | } 2456 | 2457 | } 2458 | 2459 | # Glitch Forge contracts 2460 | else if (sum("KT1NJXj24i8GDz6ZAb8XdP8RgkE2mo5s9nEG" %in% x$targetAddress) > 0) { 2461 | 2462 | # Glitch Forge purchase 2463 | if ("purchase" %in% x$parameterEntry) { 2464 | x %<>% quick_case(., entry="purchase", case="Glitch Forge purchase") 2465 | } 2466 | 2467 | # Glitch Forge unidentified 2468 | else { 2469 | x <- y 2470 | } 2471 | } 2472 | 2473 | # tzprofiles default call 2474 | else if ("default" %in% x$parameterEntry & nrow(x) == 1) { 2475 | x %<>% quick_case(., entry="default", case="tzprofiles update") 2476 | } 2477 | 2478 | # C-verso Contracts 2479 | else if(sum(cverso_contracts %in% x$targetAddress) > 0) { 2480 | 2481 | # C-verso mint 2482 | if ("mint_token" %in% x$parameterEntry) { 2483 | x %<>% quick_case(., entry="mint", case="C-Verso mint") 2484 | } 2485 | 2486 | # C-verso unidentified 2487 | else { 2488 | x <- y 2489 | } 2490 | } 2491 | 2492 | # Unidentified 2493 | else { 2494 | x <- y 2495 | } 2496 | 2497 | # Check for hDAO airdrop 2498 | if ("hDAO_batch" %in% y$parameterEntry) { 2499 | x_h <- filter(y, parameterEntry == "hDAO_batch") 2500 | y2 <-filter(y, type == "transaction") 2501 | x_h_wallets <- y2$parameterValue[[5]][[1]] 2502 | x_h_amount <- as.numeric(y2$parameterValue[[5]][[2]]) 2503 | x_h_index <- which(x_h_wallets %in% wallets) 2504 | if (length(x_h_index) > 0) { 2505 | x_h %<>% 2506 | mutate(., 2507 | xtzSent = 0, 2508 | xtzReceived = 0, 2509 | tokenID = "KT1AFA2mwNUMNd4SsujE1YYp29vd8BZejyKW_0", 2510 | tokenReceiver = x_h_wallets[x_h_index][1], 2511 | tokenAmount = sum(x_h_amount[x_h_index]), 2512 | case = "hDAO airdrop" 2513 | ) 2514 | if (sum(c("failed", "backtracked") %in% x$status) > 0) { 2515 | x_h %<>% mutate(., tokenAmount=0, case="Failed transaction") 2516 | } 2517 | x %<>% add_row(., x_h) 2518 | } 2519 | } 2520 | 2521 | # Add row(s) to income statement 2522 | if (nrow(x) > 0) { 2523 | is %<>% bind_rows(., x) 2524 | } 2525 | } 2526 | 2527 | # Adjust income statement data 2528 | is %<>% 2529 | select(., -bidKey) %>% 2530 | mutate(., case = ifelse( 2531 | is.na(case) & parameterEntry == "update_operators", 2532 | "Update operator transaction", 2533 | case 2534 | )) 2535 | -------------------------------------------------------------------------------- /functions/list_check.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | # Function to check nested lists for variables 20 | list_check <- function(x, check) { 21 | y <- NA 22 | if ((class(x) == "list" | class(x) == "data.frame") & length(x) > 0) { 23 | for (i in 1:length(x)) { 24 | if ((!is.null(names(x)[i])) && (names(x)[i] %in% check)) y <- x[[i]][[1]] 25 | else y <- list_check(x[[i]], check) 26 | if (!is.na(y)) break 27 | } 28 | } 29 | return(y) 30 | } 31 | -------------------------------------------------------------------------------- /functions/quick_case.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | # Function to quickly classify and filter common operation groups 20 | quick_case <- function(x, entry=NA, case=NA, type=1) { 21 | if (type == 1) y <- filter(x, parameterEntry == entry) 22 | else if (type == 2) y <- top_n(x, n=1, wt=id) 23 | y <- mutate(y, case=case) 24 | return(y) 25 | } 26 | -------------------------------------------------------------------------------- /functions/tzkt_api.R: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | # # 3 | # Copyright 2025 datcsv # 4 | # # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); # 6 | # you may not use this file except in compliance with the License. # 7 | # You may obtain a copy of the License at # 8 | # # 9 | # http://www.apache.org/licenses/LICENSE-2.0 # 10 | # # 11 | # Unless required by applicable law or agreed to in writing, software # 12 | # distributed under the License is distributed on an "AS IS" BASIS, # 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 14 | # See the License for the specific language governing permissions and # 15 | # limitations under the License. # 16 | # # 17 | ################################################################################ 18 | 19 | ################################################################################ 20 | # Notes: # 21 | # (1) TzKT Explorer provides free REST API and WebSocket API for accessing # 22 | # detailed Tezos blockchain data and helps developers build more services # 23 | # and applications on top of Tezos. The following functions were built for # 24 | # TzKT API (1.7.0). # 25 | # # 26 | # Additional API documentation is available at: https://api.tzkt.io/ # 27 | # # 28 | ################################################################################ 29 | 30 | # Get account operations, https://api.tzkt.io/#operation/Accounts_GetOperations 31 | tzkt_operations <- function( 32 | address, level=NA, limit=NA, span=NA, quote="usd", base="https://api.tzkt.io/" 33 | ) { 34 | sfx <- paste0("v1/accounts/", address, "/operations?quote=", quote) 35 | if (!is.na(level)) sfx <- paste0(sfx, "&level.lt=", level) 36 | if (!is.na(limit)) sfx <- paste0(sfx, "&limit=", limit) 37 | if (!is.na(span)[1]) { 38 | sfx <- paste0(sfx, "×tamp.ge=", span[1], "×tamp.le=", span[2]) 39 | } 40 | url <- paste0(base, sfx) 41 | x <- tryCatch( 42 | fromJSON(url), 43 | error=function(e) { 44 | message(e) 45 | message("Retrying in 10 seconds...") 46 | Sys.sleep(10) 47 | return(fromJSON(url)) 48 | } 49 | ) 50 | return(x) 51 | } 52 | 53 | # Get operations by hash, https://api.tzkt.io/#operation/Operations_GetByHash 54 | tzkt_operations_hash <- function( 55 | hash, quote="usd", base="https://api.tzkt.io/" 56 | ) { 57 | sfx <- paste0("v1/operations/", hash, "?quote=", quote) 58 | url <- paste0(base, sfx) 59 | x <- tryCatch( 60 | fromJSON(url), 61 | error=function(e) { 62 | message(e) 63 | message("Retrying in 10 seconds...") 64 | Sys.sleep(10) 65 | return(fromJSON(url)) 66 | } 67 | ) 68 | # Adjust reveals 69 | if (("reveal" %in% x$type) & (nrow(x) > 1)) { 70 | x %<>% filter(., type != "reveal") 71 | } 72 | return(x) 73 | } 74 | 75 | # Get bigmap by ID, https://api.tzkt.io/#operation/BigMaps_GetBigMapById 76 | tzkt_bigmap <- function(id, key, base="https://api.tzkt.io/") { 77 | sfx <- paste0("v1/bigmaps/", id, "/keys/", key) 78 | url <- paste0(base, sfx) 79 | x <- tryCatch( 80 | fromJSON(url), 81 | error=function(e) { 82 | message(e) 83 | message("Retrying in 10 seconds...") 84 | Sys.sleep(10) 85 | return(fromJSON(url)) 86 | } 87 | ) 88 | return(x) 89 | } 90 | 91 | # Get bigmap key updates, https://api.tzkt.io/#operation/BigMaps_GetKeyUpdates 92 | tzkt_bigmap_updates <- function(id, key, base="https://api.tzkt.io/") { 93 | sfx <- paste0("v1/bigmaps/", id, "/keys/", key, "/updates") 94 | url <- paste0(base, sfx) 95 | x <- tryCatch( 96 | fromJSON(url), 97 | error=function(e) { 98 | message(e) 99 | message("Retrying in 10 seconds...") 100 | Sys.sleep(10) 101 | return(fromJSON(url)) 102 | } 103 | ) 104 | return(x) 105 | } 106 | 107 | # Get quotes, https://api.tzkt.io/#operation/Quotes_Get 108 | tzkt_quote <- function(level, quote="usd", base="https://api.tzkt.io/") { 109 | sfx <- paste0("v1/quotes?level=", level) 110 | url <- paste0(base, sfx) 111 | x <- tryCatch( 112 | fromJSON(url), 113 | error=function(e) { 114 | message(e) 115 | message("Retrying in 10 seconds...") 116 | Sys.sleep(10) 117 | return(fromJSON(url)) 118 | } 119 | ) 120 | return(x) 121 | } 122 | 123 | # Get balance at level, https://api.tzkt.io/#operation/Accounts_GetBalanceAtLevel 124 | tzkt_balance <- function(address, level, base="https://api.tzkt.io/") { 125 | sfx <- paste0("v1/accounts/", address, "/balance_history/", level) 126 | url <- paste0(base, sfx) 127 | x <- tryCatch( 128 | fromJSON(url), 129 | error=function(e) { 130 | message(e) 131 | message("Retrying in 10 seconds...") 132 | Sys.sleep(10) 133 | return(fromJSON(url)) 134 | } 135 | ) 136 | x <- x / 1000000 137 | return(x) 138 | } 139 | 140 | # Get block by hash, https://api.tzkt.io/#operation/Blocks_GetByHash 141 | tzkt_block <- function(level, base="https://api.tzkt.io/") { 142 | sfx <- paste0("v1/blocks/", level) 143 | url <- paste0(base, sfx) 144 | x <- tryCatch( 145 | fromJSON(url), 146 | error=function(e) { 147 | message(e) 148 | message("Retrying in 10 seconds...") 149 | Sys.sleep(10) 150 | return(fromJSON(url)) 151 | } 152 | ) 153 | return(x) 154 | } 155 | 156 | -------------------------------------------------------------------------------- /tez-tax.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | --------------------------------------------------------------------------------