osteel / dime
Calculate your cryptoasset taxes in the UK
Installs: 21
Dependents: 0
Suggesters: 0
Security: 0
Stars: 9
Watchers: 3
Forks: 0
Open Issues: 0
Type:project
Requires
- php: ^8.2
- ext-bcmath: *
Requires (Dev)
- brick/date-time: ^0.5.1
- eventsauce/eventsauce: ^3.4
- eventsauce/message-repository-for-illuminate: ^1.0
- eventsauce/object-hydrator: ^1.3
- eventsauce/pest-utilities: ^3.4
- fakerphp/faker: ^1.21.0
- illuminate/console: ^10.17
- illuminate/database: ^10.13
- intonate/tinker-zero: ^1.2
- laravel-zero/framework: ^10.0
- laravel-zero/phar-updater: ^1.3
- laravel/pint: ^1.6
- laravel/prompts: ^0.1.5
- mockery/mockery: ^1.5.1
- nunomaduro/termwind: ^1.15
- pestphp/pest: ^2.5
- pestphp/pest-plugin-mock: ^2.0
- phpoffice/phpspreadsheet: ^1.28
- phpstan/extension-installer: ^1.2
- phpstan/phpstan: ^1.10.6
- rector/rector: ^0.18.0
- tomasvotruba/type-coverage: ^0.2.0
README
Calculate your cryptoasset taxes in the UK
About
Dime is a free and open-source command-line tool written in PHP to calculate your cryptoasset taxes in the UK.
It takes a spreadsheet of transactions as input and returns the corresponding tax figures per tax year.
Dime is primarily intended for people already familiar with the UK's cryptoassets tax rules looking for a privacy-preserving way to complete their tax return.
Dime is a project by Yannick Chenot that is also the object of a blog series.
Disclaimer
This program is Copyright (C) 2022 by Yannick Chenot.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should seek the advice of a professional accountant before using this program.
Table of contents
Installation
Standalone application 🧪
Note
This is an experimental feature. Please open an issue if the application doesn't behave as expected.
Download the binary for your operating system:
You can use the application straight away:
$ chmod +x dime-<YOUR-OS>
$ ./dime-<YOUR-OS>
Or you may want to move it to a directory that is in your system's PATH
:
$ chmod +x dime-<YOUR-OS>
$ mv dime-<YOUR-OS> /usr/local/bin/dime
You can now run dime
from anywhere instead of ./dime-<YOUR-OS>
.
Composer
Important
Requires Composer as well as PHP 8.2 and the BCMath extension.
You can instal Dime via Composer:
$ composer global require osteel/dime
Once the script is done, you should be able to run dime
from anywhere to use the application.
If not, make sure the ~/.composer/vendor/bin
directory is in your system's PATH
.
Show me how
If it's not already there, add the following line to your Bash configuration file (usually ~/.bash_profile
, ~/.bashrc
, ~/.zshrc
, etc.):
$ export PATH=~/.composer/vendor/bin:$PATH
If the file doesn't exist, create it.
Run the following command on the file you've just updated for the change to take effect:
$ source ~/.bash_profile
PHAR (Linux / Unix / macOS)
Important
Requires PHP 8.2 and the BCMath extension.
Download the PHAR archive from the latest release.
You can use the application straight away:
$ php dime
Or you may want to move it to a directory that is in your system's PATH
:
$ chmod +x dime
$ mv dime /usr/local/bin/dime
You can now run dime
from anywhere instead of php dime
.
Docker
Important
Requires Docker and sh or Bash.
Download this shell script and use it straight away:
$ sh dime.sh
It will download Dime's Docker image and pass on commands to it.
You may also want to move the script to a directory that is in your system's PATH
:
$ chmod +x dime.sh
$ mv dime.sh /usr/local/bin/dime
You can now run dime
from anywhere.
Usage
Display Dime's menu by calling the executable without arguments:
$ dime
Here is a brief description of the main commands.
Processing transactions
Pass your transaction spreadsheet to the process
command:
$ dime process transactions.csv
It will validate and process each transaction and display the corresponding tax figures or report any errors.
Tip
You will need to run this command every time you update the spreadsheet.
See the Spreadsheet format section to learn how to report your transactions.
Reviewing a tax year
Once your transaction spreadsheet has been processed you can review the corresponding tax figures whenever you like without having to run the process
command again.
To list the available tax years and pick one to review:
$ dime review
To review a specific tax year:
$ dime review 2015-2016
Calling the review
command without specifying a tax year will also display your current fiat balance (the difference between the fiat amount you put in and the fiat amount you took out).
Spreadsheet format
Here is a sample CSV file you can use as a starting point. It is recommended to read this entire section to ensure proper reporting, however.
Your spreadsheet must contain at least the following columns:
Column names are not case-sensitive and columns can be in any order. Extra columns will be ignored by the program.
Date
The expected date format is DD/MM/YYYY
(e.g. 21/10/2015
).
You can also specify the time of the transaction, although Dime doesn't need it.
Operation
Accepted values: receive
, send
, swap
, and transfer
.
Each operation requires different columns to have values (see detail below). You can also use the sample CSV file as a guide.
Receive
This operation is for transactions where you receive a cryptoasset in exchange for nothing. This happens when someone gifts you some crypto, for instance, or when you get or claim an airdrop.
receive
transactions require values for the Market value
, Received asset
, and Received quantity
columns.
Send
This operation is for transactions where you send a cryptoasset and do not receive another cryptoasset or some fiat currency in exchange. This happens when you gift someone some crypto, for instance, or when you pay for a product or service using a cryptoasset.
send
transactions require values for the Market value
, Sent asset
, and Sent quantity
columns.
Swap
This operation is for transactions where you exchange a cryptoasset (or some fiat currency) for another cryptoasset (or some fiat currency). This happens when you buy some crypto off an exchange, for instance, or when you sell some crypto, or you exchange a cryptoasset for another one.
In any case, at least one side of the transaction must be a cryptoasset.
swap
transactions require values for the Market value
, Received asset
, Received quantity
, Sent asset
, and Sent quantity
columns.
Transfer
This operation is for transactions where you transfer a cryptoasset from and to a wallet that you control. This happens when you withdraw some crypto from an exchange and send it to your hardware wallet, for instance.
transfer
transactions require values for the Sent asset
and Sent quantity
columns.
Market value
The market value of a transaction is its value expressed in pound sterling at the time of the transaction.
The market value is at the centre of Dime's calculation rules so it is crucial to report it correctly.
Rules
When the transaction includes a sent asset (and is not a transfer – transfers don't need a market value), you must use the sent asset's market value as the transaction's market value, based on the reported sent quantity (see Sent quantity section below to learn how to report it correctly).
Important
Be particularly vigilant when selling an asset for some fiat currency. It is tempting to use the received fiat amount as the market value, but if a fee was taken, the actual market value is the received amount plus the fee's market value.
When the transaction does not include a sent asset (i.e. receive
transactions), the transaction's market value is the received asset's market value.
How to find the market value
There are several ways to figure out a transaction's market value.
Keeping in mind that when a transaction includes a sent asset, its market value takes precedence:
- If the sent asset is a fiat amount, use that as the market value;
- If performing a transaction on an exchange, use the exchange's rate for the asset at the time of the transaction;
- If using a decentralised protocol, use the value reported by the corresponding blockchain's explorer (see the value between brackets in the Tokens Transferred section here, for instance);
- If none of the above applies, look up the asset on price-tracking websites such as CoinMarketCap or CoinGecko.
Tip
If any of the above amounts are expressed in a foreign currency, use HMRC's exchange rates to convert them to pound sterling.
Sometimes, while the transaction includes a sent asset, its price isn't tracked anywhere. In that case, use the received asset's market value plus the fee's market value, if you paid for that fee.
In some cases, none of the transaction's asset prices are tracked anywhere. When that happens, you are supposed to figure out the assets' fair market value, although there is no clear guidance on how to do that.
Sent asset
A sent asset must be specified for send
, swap
, and transfer
transactions.
Here is the detail of each related column.
Sent asset
This is the sent asset's symbol, or ticker (e.g. BTC
for Bitcoin or GBP
for pound sterling).
If the asset is an NFT, you can use any string value you like, so long as it is unique in your spreadsheet. I usually go for the collection's ID followed by the item's number (e.g. this CryptoPunk would be ID 0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/1008
).
Sent quantity
The quantity sent. For NFTs, that would be 1
.
Be as precise as possible and report all available decimal places.
Important
When the sent asset and the fee's currency are the same (e.g. GBP in both cases), and the fee's quantity was deducted from the sent quantity, the sent quantity must exclude the fee's quantity.
- Example #1: If you send £50 to an exchange and spend it all in a transaction that incurs a £0.50 fee, the sent quantity is 49.5.
- Example #2: If you swap 1 bitcoin for 10 ethers on an exchange and the transaction incurs a BTC 0.01 fee, the sent quantity is 0.99.
Sent asset is non-fungible
Whether the sent asset is an NFT. This is a boolean value that can be any of true
, yes
, y
, and 1
. If your spreadsheet software allows you to insert checkboxes in your cells, use that.
Received asset
A received asset must be specified for receive
and swap
transactions.
Here is the detail of each related column.
Received asset
This is the received asset's symbol, or ticker (e.g. BTC
for Bitcoin or GBP
for pound sterling).
If the asset is an NFT, you can use any string value you like, so long as it is unique in your spreadsheet. I usually go for the collection's ID followed by the item's number (e.g. this CryptoPunk would be ID 0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb/1008
).
Received quantity
The quantity received. For NFTs, that would be 1
.
Be as precise as possible and report all available decimal places.
Received asset is non-fungible
Whether the received asset is an NFT. This is a boolean value that can be any of true
, yes
, y
, and 1
. If your spreadsheet software allows you to insert checkboxes in your cells, use that.
Fee
While the related values are optional, most transactions will incur a fee.
Different values must be used depending on the context:
- If the transaction is on-chain (e.g. performed through a decentralised protocol), use the blockchain's fee (i.e. as reported by the block explorer);
- If the transaction was conducted on an exchange, use the exchange's fee, even if there is a corresponding on-chain transaction.
The reason for the latter is that exchanges usually estimate how much the fee will be and charge that estimate. This amount is almost always different from the actual fee, but that's the amount you will be charged anyway.
Tip
Be sure not to report fees that you didn't pay for. For instance, if you received an airdrop that was sent by the token issuer and the transaction includes a fee, the issuer paid for that fee, not you, so you don't have to report it.
Here is the detail of each fee-related column.
Fee currency
This is the symbol (or ticker) of the asset used to pay for the fee (e.g. BTC
for Bitcoin or GBP
for pound sterling).
Fee quantity
The quantity used for the fee.
Be as precise as possible and report all available decimal places.
Fee market value
The market value of a fee is its value expressed in pound sterling at the time of the transaction. See Market value for details.
Income
Some transactions are considered income by HMRC. A typical example is an airdrop received because of some past activity.
For instance, if you had purchased an ENS domain before November 2021, you would have received some ENS tokens as part of their airdrop. This was a reward based on something done in the past, which is considered income by HMRC.
The Income
column is a boolean value that can be any of true
, yes
, y
, and 1
. If your spreadsheet software allows you to insert checkboxes in your cells, use that.
Summary
Here is a summary of the important rules:
- Always use the sent asset's market value when available, even if the received asset is a fiat amount;
- Transfers don't need a market value;
- The sent quantity must exclude the fee's quantity when the sent asset and the fee's currency are the same;
- The sent asset's market value must be based on the reported sent quantity;
- In
receive
transactions, the market value is the received asset's market value; - Use HMRC's exchange rates to convert foreign currency amounts to pound sterling.
If you got this far you must have realised by now that reporting transactions is a bit of work. Dime grants you more control over your reporting but the trade-off is that it is your responsibility to do it well.
In other words, the returned tax figures are only as good as the spreadsheet's accuracy.
Special cases
Here is a list of edge cases that need to be reported in a certain way to be correctly processed.
Minting an NFT out of several other NFTs
Detail
Some collections allow you to collect NFTs that you can then combine to mint a new NFT through a forge. In practice, that means exchanging several NFTs for another one in a single transaction. NFTs being unique, there is no easy way to report such a transaction as a single spreadsheet row.
The way to do this in Dime is to split the transaction into as many transactions as there are NFTs involved in the creation of the new one.
Imagine you buy three different NFTs depicting animals. One is a lion (market value of £200), another one is a goat (£100), and the third one is a snake (£150). You then combine them to mint a new NFT depicting the Chimera, effectively swapping the three initial NFTs for the new one.
Instead of reporting a single transaction, you'd report the following transactions in your spreadsheet:
- Swap the lion NFT for the Chimera NFT (market value of £200);
- Swap the goat NFT for the Chimera NFT (market value of £100);
- Swap the snake NFT for the Chimera NFT (market value of £150).
While expressed as three different transactions, Dime will identify that in each case the Chimera NFT is being received, and will update its cost basis accordingly (the total amount here being £450).
How it works
Dime processes the transactions contained in your spreadsheet and saves them in a local SQLite database along with the tax figures that it computes.
The database file is located within your user directory's .dime
folder:
$ ls ~/.dime/database.sqlite
Everything is processed locally – your transactions never leave your computer.
A technical description of the architecture is available here.
Maintenance
Upgrading and deleting Dime depends on how you installed it in the first place.
Upgrade
If you have installed Dime via Composer, this is how to upgrade to a minor version (e.g. v1.1
):
$ composer global update osteel/dime
To a major version (e.g. v2.0
):
$ composer global require osteel/dime
If you have installed Dime by downloading the PHAR archive:
$ dime self-update
If you have installed Dime via Docker:
$ docker pull ghcr.io/osteel/dime:latest
Delete
If you have installed Dime via Composer:
$ composer global remove osteel/dime
If you have installed Dime by downloading the PHAR archive, you need to manually delete it from its location, e.g.:
$ rm /usr/local/bin/dime
If you have installed Dime via Docker:
$ dime delete
In all cases, you also need to delete the .dime
folder (unless you want to keep the database):
$ rm -r ~/.dime
Issue reporting
I don't take feature requests at this stage (although I will look at pull requests – see Contributing below), but I will try and address bugs as well as any issue related to reported figures.
If Dime seemingly crashes for no reason or returns numbers that appear to be wrong, please file an issue here using the relevant template.
Also, feel free to open a discussion for anything else related to the program (e.g. tax rules, how to report something in the spreadsheet, etc.). I cannot guarantee that I will personally reply, but someone else might.
Contributing
I don't take feature requests at this stage but pull requests are welcome. See CONTRIBUTING for details.
Also, make sure to open a discussion before you take on any significant work to avoid disappointment.