Skip to content

Instantly share code, notes, and snippets.

@jspiro
Last active October 29, 2023 23:37
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jspiro/27e817b85c3b6d6397c5b3d9fab9cf29 to your computer and use it in GitHub Desktop.
Save jspiro/27e817b85c3b6d6397c5b3d9fab9cf29 to your computer and use it in GitHub Desktop.
How I do my bitcoin taxes

TurboTax has no crypto support, it also doesn't support importing CSVs of transactions, and CreditKarma has no ability to import from financial institutions at all (so e.g. wealthfront is an instant dealbreaker for trying it). Square sends out terrible CSVs of transactions and does very little to help you report for the few bucks you made.

  1. Import transactions to https://app.koinly.io/transactions
  2. Set tax basis to LIFO, print https://app.koinly.io/reports/2019 to PDF
  3. Load https://app.koinly.io/transactions?page=1 and find the transactions JSON transfer in the Network tab, Copy as cURL
  4. In the terminal, paste this, and either remove the gzip line or pipe through gzip, do this for each page, e.g.:
curl 'https://api.koinly.io/api/transactions?page=1&per_page=20' \
    -XGET \
    -H 'Accept: application/json, text/plain, */*' \
    -H 'Host: api.koinly.io' \
    -H 'Accept-Language: en-us' \
    -H 'Access-Control-Allow-Credentials: true' \
    -H 'Origin: https://app.koinly.io' \
    -H 'Referer: https://app.koinly.io/' \
    -H 'User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Safari/605.1.15' \
    -H 'Connection: keep-alive' \
    -H 'Cookie: _ga=...' \
    -H 'Caches-Requests: 1' \
    -H 'X-Auth-Token: ...' | jq . > p1.json
  1. That should be enough to document the final results, but you can also make a PDF by printing each page to Preview and then dragging the first page to the main PDF (https://app.koinly.io/reports/2019).
  2. Confirm the values match Koinly:
  • Total proceeds: cat transactions.json | jq '[.transactions[] | select(.type=="sell") | .to.amount | tonumber] | add' (this should match the real 1099-B PDF)
  • Total cost-basis: cat transactions.json | jq '[.transactions[] | select(.type=="sell") | .from.cost_basis | tonumber] | add'

To check if the numbers match IRS reporting:

  1. Download wages/income transcript from https://sa.www4.irs.gov/icce-core/load/gettrans/pages/availableTranscripts.xhtml
  2. Copy/paste into MacVim
  3. :%s/Recipient:\n\n/Recipient:\r
  4. :%s/ SQUA\n.*\n.*\n.*\n.*\n.*\n.*\n.*\n\(.*\)\n.*\n.*\n.*\n.*\n\(.*\)/%%%\1,$$$\2/g
  5. :g!/%%%/d
  6. :%s/.*\(..-..-....\),.*\$\(.*\)/\1,\2
  7. Save as a CSV and make sure the numbers match up (roughly: Square seems to be flooring all numbers reported to IRS, not rounding).

Import to TT:

  1. Convert the transactions to a CSV format we can use: cat transactions.json | jq -r '.transactions[] | select(.type=="sell") | [(.from.amount + " BTC"), .date, "VARIOUS", (.to.amount | tonumber), (.from.cost_basis | tonumber)] | @csv' | sed 's/T............Z//g' | gsed "1s/^/Desc,Sold,Acquired,Proceeds,Basis\n/" > out.csv
  2. ./csv2txf.py
  3. Import the txf to TurboTax.
  4. Edit the entry and change the institution name to Square, Inc.
  5. Don't edit any individual entires, just check that the values are right.
  6. You might need to edit the name of the institution in the worksheet (CMD-t)
  7. You might need to answer a question about whether you sold crypto in the worksheets.
#!/usr/bin/python
# CSV input format: Desc,Sold,Acquired,Proceeds,Basis
# TXF Format
# ---------------------------------------------------------------------------------
# Header [V-A-D-^]
# Records [T-N-C-L-P-D-D-$-$-$-^]
# N321 8949-A, short term
# N323 8949-A, long term
# N682 8949-A, wash sale
# N711 8949-B, short term
# N713 8949-B, long term
# N718 8949-B, wash sale
# N712 8949-C, short term
# N714 8949-C, long term
# V0[version] V042
# A[application] ATax Tool
# D[date of export] 02/11/2006
# ^ ^
# T[type: specific, detail] TD
# N[number: 321=short, 323=long, 682=wash] N321
# C[copy: 1] C1
# L[line: 1] L1
# P[description] P100 Nokia (NOK)
# D[buy date: mm/dd/yyyy] D06/01/2005
# D[sell date: mm/dd/yyyy] D06/01/2005
# $[buy price] $1000.00
# $[sell price] $1050.46
# $[wash loss, skip on zero] $280.81
# ^ ^
# ---------------------------------------------------------------------------------
import csv
from datetime import datetime
with open(
datetime.now().strftime("%Y-%m-%d") + " - square cash - bitcoin transactions.txf",
"w",
) as text_file:
# Write the header
text_file.write("V042\n")
text_file.write("ABitcoinTaxes\n")
text_file.write("D " + datetime.now().strftime("%m/%d/%Y") + "\n")
text_file.write("^\n")
with open("out.csv", "r") as csv_file:
for row in csv.DictReader(csv_file):
text_file.write("TD\n")
text_file.write("N711\n")
text_file.write("C1\n")
text_file.write("L1\n")
text_file.write("P" + row["Desc"] + "\n")
text_file.write("DVARIOUS\n")
text_file.write(
"D"
+ datetime.strptime(row["Sold"], "%Y-%m-%d").strftime("%m/%d/%Y")
+ "\n"
)
text_file.write("$%.2f\n" % float(row["Basis"]))
text_file.write("$%.2f\n" % float(row["Proceeds"]))
text_file.write("^\n")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment