USA to drop universal application: implement per-wallet application
Discussion on this topic started here: https://github.com/eprbell/rp2/discussions/134
Some useful links on this topic:
-
If you pay taxes in the US watch this informative interview by Andreas Antonopulous on per-wallet application in the US.
-
This Reddit thread describes the issue in detail. I asked a few questions there to clarify semantics. More clarification here.
-
A brief summary of the topic by Bitcoin.tax.
-
Another video summary by Crypto Tax Girl.
-
Another summary by JustinCPA on Reddit.
This is what the great magnificent supercomputer in the sky aka CoPilot had to say about this:
To modify the project to track transactions by individual wallets and calculate the cost basis based on the purchase price for the crypto asset in each wallet, you will need to make changes to several parts of the codebase. Here is a step-by-step guide to help you implement this feature:
-
Update the
TransactionSetclass to include wallet information. -
Modify the
InputDataclass to handle wallet-specific transactions. -
Update the
GainLossSetclass to calculate gain/loss based on wallet-specific transactions. -
Modify the
tax_engine.pyto use the updated classes and methods.
Step 1: Update the TransactionSet Class
Add wallet information to the TransactionSet class and ensure it can handle transactions for different wallets.
Step 2: Modify the InputData Class
Update the InputData class to handle wallet-specific transactions. This involves adding methods to filter transactions by wallet.
Step 3: Update the GainLossSet Class
Modify the GainLossSet class to calculate gain/loss based on wallet-specific transactions.
Step 4: Modify tax_engine.py
Update the tax_engine.py to use the updated classes and methods.
Here is an example of how you can modify the tax_engine.py:
def _create_unfiltered_taxable_event_set(configuration: Configuration, input_data: InputData) -> Dict[str, TransactionSet]:
wallet_taxable_event_sets: Dict[str, TransactionSet] = {}
for wallet in input_data.wallets:
taxable_event_set: TransactionSet = TransactionSet(configuration, "MIXED", input_data.asset, MIN_DATE, MAX_DATE)
for transaction_set in [
input_data.get_unfiltered_in_transaction_set(wallet),
input_data.get_unfiltered_out_transaction_set(wallet),
input_data.get_unfiltered_intra_transaction_set(wallet),
]:
for entry in transaction_set:
transaction = cast(AbstractTransaction, entry)
if transaction.is_taxable():
taxable_event_set.add_entry(transaction)
wallet_taxable_event_sets[wallet] = taxable_event_set
return wallet_taxable_event_sets
def _create_unfiltered_gain_and_loss_set(
configuration: Configuration, accounting_engine: AccountingEngine, input_data: InputData, wallet_taxable_event_sets: Dict[str, TransactionSet]
) -> Dict[str, GainLossSet]:
wallet_gain_loss_sets: Dict[str, GainLossSet] = {}
for wallet, taxable_event_set in wallet_taxable_event_sets.items():
gain_loss_set: GainLossSet = GainLossSet(configuration, input_data.asset, MIN_DATE, MAX_DATE)
new_accounting_engine: AccountingEngine = accounting_engine.__class__(accounting_engine.years_2_methods)
taxable_event_iterator: Iterator[AbstractTransaction] = iter(cast(Iterable[AbstractTransaction], taxable_event_set))
acquired_lot_iterator: Iterator[InTransaction] = iter(cast(Iterable[InTransaction], input_data.get_unfiltered_in_transaction_set(wallet)))
new_accounting_engine.initialize(taxable_event_iterator, acquired_lot_iterator)
try:
gain_loss: GainLoss
taxable_event: AbstractTransaction
acquired_lot: Optional[InTransaction]
taxable_event_amount: RP2Decimal
acquired_lot_amount: RP2Decimal
total_amount: RP2Decimal = ZERO
(taxable_event, acquired_lot, taxable_event_amount, acquired_lot_amount) = _get_next_taxable_event_and_acquired_lot(
new_accounting_engine, None, None, ZERO, ZERO
)
while taxable_event:
AbstractTransaction.type_check("taxable_event", taxable_event)
if acquired_lot is None:
raise RP2RuntimeError("Parameter 'acquired_lot' is None")
InTransaction.type_check("acquired_lot", acquired_lot)
if taxable_event_amount == acquired_lot_amount:
gain_loss = GainLoss(configuration, taxable_event_amount, taxable_event, acquired_lot)
total_amount += taxable_event_amount
gain_loss_set.add_entry(gain_loss)
(taxable_event, acquired_lot, taxable_event_amount, acquired_lot_amount) = _get_next_taxable_event_and_acquired_lot(
new_accounting_engine, None, None, ZERO, ZERO
)
elif taxable_event_amount < acquired_lot_amount:
gain_loss = GainLoss(configuration, taxable_event_amount, taxable_event, acquired_lot)
total_amount += taxable_event_amount
gain_loss_set.add_entry(gain_loss)
(taxable_event, acquired_lot, taxable_event_amount, acquired_lot_amount) = _get_next_taxable_event_and_acquired_lot(
new_accounting_engine, taxable_event, acquired_lot, ZERO, acquired_lot_amount - taxable_event_amount
)
else:
gain_loss = GainLoss(configuration, acquired_lot_amount, taxable_event, acquired_lot)
total_amount += acquired_lot_amount
gain_loss_set.add_entry(gain_loss)
(taxable_event, acquired_lot, taxable_event_amount, acquired_lot_amount) = _get_next_taxable_event_and_acquired_lot(
new_accounting_engine, taxable_event, None, taxable_event_amount - acquired_lot_amount, ZERO
)
except AcquiredLotsExhaustedException:
raise RP2ValueError("Total in-transaction crypto value < total taxable crypto value") from None
except TaxableEventsExhaustedException:
pass
wallet_gain_loss_sets[wallet] = gain_loss_set
return wallet_gain_loss_sets
def compute_tax(configuration: Configuration, accounting_engine: AccountingEngine, input_data: InputData) -> Dict[str, ComputedData]:
Configuration.type_check("configuration", configuration)
AccountingEngine.type_check("accounting_engine", accounting_engine)
InputData.type_check("input_data", input_data)
wallet_taxable_event_sets = _create_unfiltered_taxable_event_set(configuration, input_data)
wallet_gain_loss_sets = _create_unfiltered_gain_and_loss_set(configuration, accounting_engine, input_data, wallet_taxable_event_sets)
computed_data: Dict[str, ComputedData] = {}
for wallet, gain_loss_set in wallet_gain_loss_sets.items():
computed_data[wallet] = ComputedData(
input_data.asset,
wallet_taxable_event_sets[wallet],
gain_loss_set,
input_data,
configuration.from_date,
configuration.to_date,
)
return computed_data
This example assumes that the InputData class has methods like get_unfiltered_in_transaction_set(wallet), get_unfiltered_out_transaction_set(wallet), and get_unfiltered_intra_transaction_set(wallet) to retrieve transactions for a specific wallet. You will need to implement these methods and ensure that the
TransactionSet and GainLossSet classes can handle wallet-specific transactions.
By following these steps, you can modify the project to track transactions by individual wallets and calculate the cost basis based on the purchase price for the crypto asset in each wallet.
It goes on to say that AccountingEngine of course needs to be modified:
class AccountingEngine:
def __init__(self, years_2_methods: Dict[int, str]):
self.years_2_methods = years_2_methods
self.wallets = {}
def initialize(self, taxable_event_iterator: Iterator[AbstractTransaction], acquired_lot_iterator: Iterator[InTransaction], wallet: str):
self.wallets[wallet] = {
"taxable_event_iterator": taxable_event_iterator,
"acquired_lot_iterator": acquired_lot_iterator,
}
def get_next_taxable_event_and_amount(
self,
wallet: str,
taxable_event: Optional[AbstractTransaction],
acquired_lot: Optional[InTransaction],
taxable_event_amount: RP2Decimal,
acquired_lot_amount: RP2Decimal,
) -> Tuple[AbstractTransaction, Optional[InTransaction], RP2Decimal, RP2Decimal]:
wallet_data = self.wallets[wallet]
taxable_event_iterator = wallet_data["taxable_event_iterator"]
acquired_lot_iterator = wallet_data["acquired_lot_iterator"]
# Logic to get the next taxable event and acquired lot for the specified wallet
# ...
def get_acquired_lot_for_taxable_event(
self,
wallet: str,
taxable_event: AbstractTransaction,
acquired_lot: Optional[InTransaction],
taxable_event_amount: RP2Decimal,
acquired_lot_amount: RP2Decimal,
) -> Tuple[AbstractTransaction, Optional[InTransaction], RP2Decimal, RP2Decimal]:
wallet_data = self.wallets[wallet]
acquired_lot_iterator = wallet_data["acquired_lot_iterator"]
# Logic to get the acquired lot for the specified taxable event and wallet
# ...
Basically, just adding wallet storage to each part of the engine. Is there something I'm missing? This is AI generated, so please go over it carefully.
Thanks, I'll read it. I had some rough ideas on how to approach the problem:
- do queue analysis to understand which wallet each lot ends up at after transfers;
- do the tax analysis wallet by wallet, rather than globally (as we do now);
- somehow normalize/unify the results in the same report.
My only problem right now is finding the time to work on it...
I think we can take it piece by piece. The first piece is to modify the initial reading in of the data to sort the different lots into different wallets. We can probably start there and build out some tests for it. I should have about the next month or so to work on and submit code. I don't think it will take that much time as long as we are pretty systematic about it.
For example, I think the first step is to create a function in tax_engine.py that sorts the transactions in to wallets. We can create the function now and write an isolated test for it and get a PR for that.
I guess this function would sort in and out transactions pretty easily, just whatever exchange they happened on. Then, intra transaction will be split into non-taxable in and out transactions in their respective wallets.
I think this handles this first step right?
- do queue analysis to understand which wallet each lot ends up at after transfers;
Then we can cycle through the wallets probably in a multithread way to process all the transactions using the current engine. That will cover the next step:
- do the tax analysis wallet by wallet, rather than globally (as we do now);
And finally merge all the GainLossSets for the final report. Am I missing something big?
I can probably put the code together as long as you can review it by the end of the year.
Yes, that sounds reasonable, however I'll add a few more considerations that complicate the picture slightly:
- Queue analysis (or transfer analysis) isn't simply about tracking lots and where they go: transferring can split a lot into parts. E.g. if I buy 1 BTC on CB and then send 0.5 BTC to a HW, I started with one lot and, after transferring, I ended up with two lots.
- The tax engine should be able to work using either universal application or per wallet application.
- Selection of which one to use should be left to the country plugin.
- Additionally, some countries (like the US) support universal up to a certain year (2024 for the US), then per wallet: this should also be reflected in the country plugin.
There are additional complications such as which method to use for transfers (FIFO, LIFO, etc.). Some options:
- Just use FIFO;
- Same as the accounting method;
- Let user select a method that may be different than the accounting method.
I think we should start option one or two.
I think we should write a brief high level design of this feature first: let me see if I can come up with a quick document in the weekend, to start the discussion.
Sorry, I guess I didn't realize until just now that this only applies to 2025, so for when we file taxes in 2026. For some reason, I thought we had to have this ready for filing taxes in 2025. I was in a panic. I guess we still have time, but if you outline what is needed I can try to have a whack at it.
Yes, according to the Reddit thread, new rules are effective from 1/1/2025. So we have 1 year to figure it out.
I'm making progress on the design of per-wallet application but it is still unfinished. I realized we can apply the existing accounting method layer to the new logic to pick which lot to transfer, which is nice. However we need a few additions to the existing infrastructure:
- add
add_acquired_lot()method toAbstractAcquiredLotCandidates - add a
get_artificial_id_from_row()function to unify management of artificial transaction row id (which are negative): the new design creates one artificial InTransaction for each IntraTransaction and this new transaction needs an artificial id.
Here's the unfinished design so far. How to switch from universal to per-wallet from one year to the next is still TBD.
Okay, I just looked up details about Japan, and they use universal wallet and you can make use of FIFO, LIFO, etc... based on all of your holdings as a whole. So, we will have to combine universal wallet with FIFO, etc... Does your plan account for that? Honestly, I'm okay with the current system of FIFO and universal wallet if we can't implement universal wallet and LIFO for example. But, it is something that we will probably need in the future to support all countries.
Universal application + FIFO/LIFO/HIFO/LOFO is already supported today (you can even change from one accounting method to another year over year). See: https://github.com/eprbell/rp2/blob/main/docs/user_faq.md#can-i-change-accounting-method. Based on what you're saying it sounds like we can add more accounting methods here: https://github.com/eprbell/rp2/blob/main/src/rp2/plugin/country/jp.py#L41
The design I'm working on supports any combination of per-wallet/universal application + FIFO/LIFO/HIFO/LOFO (including changing from one combination to another year over year). This high-generality approach is proving both interesting and hard to do, so it will require some more time to iron out all the details. It's good that we can reuse the existing accounting method infrastructure for lot selection in transfers, but the problem goes deeper than I thought. When I'm closer to something presentable, we can discuss it and brainstorm a bit.
The per-wallet application design is more or less complete. It can still be improved, but it captures most of the concepts: feel free to take a look and let me know what you think. Next I will probably do a bit of prototyping to make sure the ideas behind the design hold water.
@eprbell I read through it and it looks pretty sound. I'll have to give it some time and read through it again just to double check, but I think this will handle what we need. Thanks for working it out. It looks like a lot of work and you gave some good examples.
I'm making good progress on the implementation and unit testing of the transfer analysis function. Sorry, @macanudo527, I know you expressed some interest in working on this: I wanted to write a prototype to verify the design, but ended up finding more and more corner cases, and adjusting the code accordingly to deal with them. So basically what started as a prototype is becoming the real thing. I will open a PR for this though: it would be good to get your feedback before merging.
No worries, I can just spend time on some of the other core functions instead. Looking forward to it. I'll be out for the end of the year (Dec 23rd - Jan 7th), but after that I can look at it.
Sounds good (we're in no rush). Have a great time during the holidays!
US tax payers, watch this informative interview by Andreas Antonopulous on per-wallet application in the US.
Hi. Hope you don't mind me commenting as a non-code-contributing member of the RP2 community.
I watched the Andreas Antonopulous interview. Honestly, I found it extremely difficult to follow due to him constantly interrupting the guest. I found this video by "Crypto Tax Girl" to be much more clear and easy to follow. If you have a chance to watch it, I'd be interested if you feel it lines up with your understanding of these changes.
A have a few questions if I may:
- Under the new per-wallet tracking with FIFO, if one transfers crypto from wallet A to wallet B, presumably the first crypto in wallet A will be transferred to wallet B. But when it gets to wallet B, where does it go in the FIFO queue? Does it automatically go to the end with a new date (i.e. the date of transfer), or does it get slotted into the queue at some point in the middle based on the original date of purchase? (Hopefully that makes sense.)
- There has been talk (including in both videos) about the "safe harbor" provision. I've seen two extremes for what one needs to do by the end of 2024 to comply. One is to simply declare the rules one is going to use going forward. (This is like what Crypto Tax Girl proposes.) The other is to fully document one's crypto holdings and how cost basis is allocated to each wallet. The latter approach would seem to be infeasible for RP2 users, given that the code changes are still in process, right? Is there a way -- even if somewhat manual -- to know at this point how RP2 will allocate cost basis to each wallet so that this information can be documented by the end of 2024?
Thoughts?
Thanks for all you do to make RP2 available and up-to-date! Much appreciated.
Feedback is always welcome from anybody (code-contributing or not). Thanks for the video: I haven't watched it yet, but I will in the weekend. I think the main takeaway from Andreas' video is to follow the procedure he and his guest recommended before 12/31/24: basically consolidate everything into one wallet (if possible). The Description in the video summarizes the steps to take. This will simplify accounting going forward.
As for your questions:
- The current thinking is to let the user select two separate things: the transfer semantics and the accounting method. The first one is part of the new feature being developed to support per-wallet application, the second one is already available today (and doesn't change). Either of these two can be FIFO, LIFO, HIFO or LOFO. See the design document). So you could use FIFO for transfers and then HIFO as the per-wallet accounting method after transfers. However this feature is still in development and may change: take the above with a grain of salt. Again, it may be a good idea to consolidate everything before EOY as suggested by Andreas.
- The first feature (document each lot's cost basis) won't be supported. The second one will (see answer above).
Hope this helps.
Thank you for the reply!
FWIW, I reviewed the design document -- twice -- but since it's very code centric, and I'm not familiar with the overall application design, I wasn't able to understand all that much about the approach being taken. (That's not a issue. It just is what it is.)
Regarding your reply, let me see if I understand:
So there are "transfer semantics" and "accounting method", each of which could be FIFO, LIFO, HIFO, or LOFO. Does that mean that if "transfer" is set to FIFO and "accounting" is set to HIFO that, when a transfer is done, the basis with the oldest date (first in) will be moved to the new wallet. And, similarly, when a token is sold within a wallet, the basis with the highest price (highest in) will be associated with the sale?
Am I understanding that correctly?
Assuming I am, I'm still not clear what happens to a token when it is transferred to another wallet and "transfer semantics" is FIFO. Does it get assign a new "in date" in the destination wallet or does it retain its original "in date" from when it was purchased?
In my original post I asked it this way:
Under the new per-wallet tracking with FIFO, if one transfers crypto from wallet A to wallet B, presumably the first crypto in wallet A will be transferred to wallet B. But when it gets to wallet B, where does it go in the FIFO queue? Does it automatically go to the end with a new date (i.e. the date of transfer), or does it get slotted into the queue at some point in the middle based on the original date of purchase?
At the time I imagined each wallet would have a "queue" of transactions, but now I understand it's probably more like a pool of transactions that can be sorted in any way at runtime, as needed, based on whether a transfer is being done ("transfer semantics") or a sell is being done ("accounting method"). Is that correct?
That being the case, I would guess that the transferred token (and corresponding basis) would retain the original purchase date even after it is moved to the destination wallet. Here's an example (a variant of yours from the design):
- 1/1:
InTransactionof 10 BTC on Coinbase - 2/1:
InTransactionof 5 BTC on Kraken - 3/1:
IntraTransactionof 4 BTC from Coinbase to Kraken - 4/1:
OutTransactionof 2 BTC from Kraken
If both "transfer semantics" and "accounting method" are FIFO, does that mean that the OutTransaction on 4/1 will use the Coinbase transaction basis from 1/1 or the Kraken transaction basis from 2/1? I would assume the former. In other words, the original "in date" associated with the 4 BTC moved from Coinbase to Kraken will be retained, and when FIFO is used for the OutTransaction, 2 of those 4 BTC will be sold, since 1/1 is the new "first in" date of the Kraken wallet (even though, technically, the 5 BTC bought on Kraken on 2/1 were "first in" prior to the transfer on 3/1 having occurred).
Does that make any sense? Hopefully. Thoughts?
As for the second question, I'm not sure what you mean by this:
- The first feature (document each lot's cost basis) won't be supported. The second one will (see answer above).
I understand that, per Andreas, it probably makes sense to try and consolidate wallets as much as possible. But are you going to actually make a "declaration" before 1/1/25 and either email it to yourself or use the blockchain time-stamping approach Andreas suggested to have something that can be provided to the IRS, if needed, as proof of claiming "safe harbor"? And, if so, what is that declaration going to contain?
Thanks!
I'm replying inline, however keep in mind that what I'm saying is based on my current ideas for a design that is still in flux. So don't make tax decisions solely based on what I'm describing here, because it may change. This is why I was highlighting Andreas' solution: it makes your crypto accounting simple and clear, regardless of what happens with tax software.
So there are "transfer semantics" and "accounting method", each of which could be FIFO, LIFO, HIFO, or LOFO. Does that mean that if "transfer" is set to FIFO and "accounting" is set to HIFO that, when a transfer is done, the basis with the oldest date (first in) will be moved to the new wallet. And, similarly, when a token is sold within a wallet, the basis with the highest price (highest in) will be associated with the sale?
Yes to both questions. The transfer semantics is what is used to populate per-wallet queues from the universal queue that is used up to the end of 2024 (it is also used when transferring from one per-wallet queue to another).
Assuming I am, I'm still not clear what happens to a token when it is transferred to another wallet and "transfer semantics" is FIFO. Does it get assign a new "in date" in the destination wallet or does it retain its original "in date" from when it was purchased?
Good question. The current idea is to create an artificial InTransaction in the "to" wallet. This artificial InTransaction has:
- timestamp: same as the IntraTransaction;
- crypto_in: minimum of IntraTransaction crypto_received and remaining amount in the from lot that was selected with transfer semantics;
- spot_price: same as the from lot that was selected with transfer semantics.
Under the new per-wallet tracking with FIFO, if one transfers crypto from wallet A to wallet B, presumably the first crypto in wallet A will be transferred to wallet B. But when it gets to wallet B, where does it go in the FIFO queue? Does it automatically go to the end with a new date (i.e. the date of transfer), or does it get slotted into the queue at some point in the middle based on the original date of purchase?
I think this was already answered above. It goes into a new queue that is specific to wallet B (that's the whole idea behind per-wallet application). However you need to consider the case in which you have 1 BTC in wallet A and you transfer 0.5 BTC to wallet B. In this case you're splitting the original lot. Currently this is captured by leaving the 1 BTC in the queue of wallet A and creating an artificial transaction for 0.5 BTC in wallet B. The two transactions are linked and are updated together by the tax engine (for more on this check this).
At the time I imagined each wallet would have a "queue" of transactions, but now I understand it's probably more like a pool of transactions that can be sorted in any way at runtime, as needed, based on whether a transfer is being done ("transfer semantics") or a sell is being done ("accounting method"). Is that correct?
Not quite: see explanation above about one queue per wallet.
That being the case, I would guess that the transferred token (and corresponding basis) would retain the original purchase date even after it is moved to the destination wallet. Here's an example (a variant of yours from the design): * 1/1:
InTransactionof 10 BTC on Coinbase * 2/1:InTransactionof 5 BTC on Kraken * 3/1:IntraTransactionof 4 BTC from Coinbase to Kraken * 4/1:OutTransactionof 2 BTC from Kraken If both "transfer semantics" and "accounting method" are FIFO, does that mean that theOutTransactionon 4/1 will use the Coinbase transaction basis from 1/1 or the Kraken transaction basis from 2/1? I would assume the former. In other words, the original "in date" associated with the 4 BTC moved from Coinbase to Kraken will be retained, and when FIFO is used for theOutTransaction, 2 of those 4 BTC will be sold, since 1/1 is the new "first in" date of the Kraken wallet (even though, technically, the 5 BTC bought on Kraken on 2/1 were "first in" prior to the transfer on 3/1 having occurred).
In your example, using FIFO for everything, the 4/1 OutTransaction would use the 2/1 InTransaction. Note that the Kraken queue would also have an arificial InTransaction on 3/1 containing 4 BTC and linked to the 1/1 transaction. But in your example the artificial transaction is not exercised because the 2/1 transaction has enough funds to cover the 2 BTC of the OutTransaction.
If the OutTransaction had, say, 7 BTC instead of 2, then the code would use first the entire 2/1 lot and then 2 BTC from the artficial InTransaction (this also causes its parent transaction 1/1 to be updated as explained here).
As for the second question, I'm not sure what you mean by this:
- The first feature (document each lot's cost basis) won't be supported. The second one will (see answer above).
I mean that RP2 won't let the user select which lot goes into which wallet queue arbitrarily. RP2 will take an algorithmic approach: the user selects the transfer semantics and the code moves the funds around.
I understand that, per Andreas, it probably makes sense to try and consolidate wallets as much as possible. But are you going to actually make a "declaration" before 1/1/25 and either email it to yourself or use the blockchain time-stamping approach Andreas suggested to have something that can be provided to the IRS, if needed, as proof of claiming "safe harbor"? And, if so, what is that declaration going to contain?
This is probably a question for your tax advisor, but the rough idea is to move everything to one single wallet and then take snapshots of all accounts, generate an RP2 report and timestamp everything on Dec 31st. By moving to a single wallet you're essentially causing universal and per-wallet approach to match, because there is now only one wallet having one queue with all the funds.
Thanks for asking questions and engaging in conversation. It's good to brainstorm the ideas behind the design and see if they hold water.
Thanks for the reply!
Agreed about the conversation and brainstorming. Even though I'm not coding this, I find it very helpful for my own understanding.
If both "transfer semantics" and "accounting method" are FIFO, does that mean that the OutTransaction on 4/1 will use the Coinbase transaction basis from 1/1 or the Kraken transaction basis from 2/1? I would assume the former. In other words, the original "in date" associated with the 4 BTC moved from Coinbase to Kraken will be retained, and when FIFO is used for the OutTransaction, 2 of those 4 BTC will be sold, since 1/1 is the new "first in" date of the Kraken wallet (even though, technically, the 5 BTC bought on Kraken on 2/1 were "first in" prior to the transfer on 3/1 having occurred).
In your example, using FIFO for everything, the 4/1 OutTransaction would use the 2/1 InTransaction. Note that the Kraken queue would also have an arificial InTransaction on 3/1 containing 4 BTC and linked to the 1/1 transaction. But in your example the artificial transaction is not exercised because the 2/1 transaction has enough funds to cover the 2 BTC of the OutTransaction.
This was a surprise to me, so I decided to post basically this exact question on the Reddit forum to see if @JustinCPA would respond, which he did. He seems to say the opposite of what you've said.
I feel like this could be an issue for the current design -- at least for US taxpayers, and assuming @JustinCPA is correct.
Thoughts?
Sounds like you found a bug in the design! Thanks for getting in the weeds and asking JustinCPA: his explanation is convincing. The bug is that the artificial InTransaction was using the timestamp of the transfer instead of the timestamp of the from InTransaction. I already fixed the code so that the behavior is as explained by Justin. I will be posting some initial code for the transfer analysis algorithm in a PR soon (together with unit tests).
Great! Thanks!
One thing that comes to mind: You may well be taking care of this already, so forgive me if you are, but you may want to make sure that transfers, if they are using the timestamp of the "from" InTransaction, do not inadvertently allow other invalid transactions to occur. An example is probably needed to explain:
- 1/1:
InTransactionof 10 BTC on Coinbase - 2/1:
OutTransactionof 1 BTC from Kraken (!! INVALID !!) - 3/1:
IntraTransactionof 3 BTC from Kraken to Binance (!! INVALID !!) - 4/1:
IntraTransactionof 5 BTC from Coinbase to Kraken
Of course the transactions on 2/1 and 3/1 seem obviously invalid when looked at like that. But, I could imagine a scenario where the 5 BTC transferred on 4/1 from Coinbase to Kraken inherit the "from" InTransaction date of 1/1, making the Kraken OutTransaction on 2/1 and IntraTransaction on 3/1 seem valid. But obviously they're not (because the 5 BTC has not been transferred yet). Make sense?
Keep in mind I don't understand the overall app design or the design for these changes. I'm just looking at this as an outsider.
Thanks!
So there are "transfer semantics" and "accounting method", each of which could be FIFO, LIFO, HIFO, or LOFO. Does that mean that if "transfer" is set to FIFO and "accounting" is set to HIFO that, when a transfer is done, the basis with the oldest date (first in) will be moved to the new wallet. And, similarly, when a token is sold within a wallet, the basis with the highest price (highest in) will be associated with the sale?
Yes to both questions. The transfer semantics is what is used to populate per-wallet queues from the universal queue that is used up to the end of 2024 (it is also used when transferring from one per-wallet queue to another).
One more thought: Is it possible to use different transfer semantics to:
- populate per-wallet queues from the universal queue, and,
- transfer from one wallet to another in 2025 and beyond?
Don't think that I would need this, but -- if I'm understanding correctly -- others may. Take a look at this post for context. Basically, I think some may want to use HIFO for populating the wallets from the universal queue, and then FIFO going forward (as I understand that is required for "global allocation" / non-spec-ID).
One thing that comes to mind: You may well be taking care of this already, so forgive me if you are, but you may want to make sure that transfers, if they are using the timestamp of the "from"
InTransaction, do not inadvertently allow other invalid transactions to occur. An example is probably needed to explain:* 1/1: `InTransaction` of 10 BTC on Coinbase * 2/1: `OutTransaction` of 1 BTC from Kraken (!! INVALID !!) * 3/1: `IntraTransaction` of 3 BTC from Kraken to Binance (!! INVALID !!) * 4/1: `IntraTransaction` of 5 BTC from Coinbase to KrakenOf course the transactions on 2/1 and 3/1 seem obviously invalid when looked at like that. But, I could imagine a scenario where the 5 BTC transferred on 4/1 from Coinbase to Kraken inherit the "from"
InTransactiondate of 1/1, making the KrakenOutTransactionon 2/1 andIntraTransactionon 3/1 seem valid. But obviously they're not (because the 5 BTC has not been transferred yet). Make sense?
Ah, good point. This means that my previous approach was only 50% wrong :-) because the artificial transaction needs both timestamps: one for holding period and the other for fund availability. Let me think a bit on how to best model this: we probably need a subclass of InTransaction to capture this.
Keep in mind I don't understand the overall app design or the design for these changes. I'm just looking at this as an outsider.
No worries: your feedback as a user has been very valuable. Keep it coming!
So there are "transfer semantics" and "accounting method", each of which could be FIFO, LIFO, HIFO, or LOFO. Does that mean that if "transfer" is set to FIFO and "accounting" is set to HIFO that, when a transfer is done, the basis with the oldest date (first in) will be moved to the new wallet. And, similarly, when a token is sold within a wallet, the basis with the highest price (highest in) will be associated with the sale?
Yes to both questions. The transfer semantics is what is used to populate per-wallet queues from the universal queue that is used up to the end of 2024 (it is also used when transferring from one per-wallet queue to another).
One more thought: Is it possible to use different transfer semantics to:
* populate per-wallet queues from the universal queue, and, * transfer from one wallet to another in 2025 and beyond?Don't think that I would need this, but -- if I'm understanding correctly -- others may. Take a look at this post for context. Basically, I think some may want to use HIFO for populating the wallets from the universal queue, and then FIFO going forward (as I understand that is required for "global allocation" / non-spec-ID).
Interesting: the current design allows for changing transfer semantics and accounting method year over year in any combination supported by the country plugin. What you're describing would be an extra one-time only transfer semantics for initial per-wallet queue population: this is not supported yet. With the existing design you could select HIFO transfer semantics in 2025 and then switch to FIFO in following years: not exactly what you're asking for but it's an approximation.
I think we should finish the basic design and implementation of per-wallet application and then we can think about something like this as a potential advanced feature.
I think we should finish the basic design and implementation of per-wallet application and then we can think about something like this as a potential advanced feature.
Of course. Just figured I'd mention it for your awareness. (I personally don't anticipate needing it.)
With the existing design you could select HIFO transfer semantics in 2025 and then switch to FIFO in following years: not exactly what you're asking for but it's an approximation.
I suppose, though I don't think that would comply with the IRS requirements. I'm no expert, but my understanding is that it has to either be FIFO or specific identification going forward.
I totally get your point, though. "One step at a time" :-)
With the existing design you could select HIFO transfer semantics in 2025 and then switch to FIFO in following years: not exactly what you're asking for but it's an approximation.
I suppose, though I don't think that would comply with the IRS requirements. I'm no expert, but my understanding is that it has to either be FIFO or specific identification going forward.
The tax engine isn't US-specific so it allows this general behavior: then the country plugin can impose constraints as needed on a country-specific basis.
Just wanted to share this post. It's a newer @JustinCPA post than the one posted above. I found it quite helpful.
I'm trying to make sure I have all my proverbial "ducks in a row" before the end of the year.
From Justin's post:
Global Allocation Method Global Allocation Method is one option for performing the migration. This method focuses on assigning a governing "rule" to your unused basis for how the allocation should be performed. In other words, a rule like "lowest cost basis to highest balance" is perfect. What does this mean? Lets look at a scenario.
You have 1 ETH in Wallet A, 5 ETH in Wallet 5, and 10 ETH in Wallet C for a total of 16 ETH. Assigning the "lowest cost basis to highest balance" global allocation rule, we would go to your spreadsheet with all your tax lots of unused basis as of 11:59pm 12/31/2024 and you would start with the lowest cost basis lots. Lot by lot, you would assign them Wallet C first, until you reached 10 ETH in that new pool, then you would take the next lowest cost basis tax lots and assign them to Wallet B until 5 ETH have been assigned to that pool. Finally, the remaining tax lots (which will be the highest cost basis) will be assigned to Wallet A.
Other examples include: "Oldest tax lots to highest balance", "Oldest tax lots to least active wallet", "Highest cost basis to lowest balance" etc.
Specific Allocation Method This method does not focus on assigning a rule, but rather allows the taxpayer to specifically allocate each unit as they see fit. In other words, taking that spreadsheet with the pool of unused basis, a taxpayer could go line by line and assign each tax lot to the wallet or exchange they want, until they reach the proper amount of assets held in that wallet/exchange.
As I understand it, one has to use either "Global Allocation" or "Specific Allocation" to determine how the basis from Universal application is applied to individual wallets. ("Global Allocation" sounds much easier to me than "Specific Allocation", so that's what I'm planning on.)
The thing is, I understand that RP2 is intending to support different methods for the transition from Universal to Per-wallet, such as FIFO. But as I think about it more, it feels like that is insufficient. (Either that or I'm missing something -- which is very possible.)
A method such as FIFO takes care of pulling basis from the Universal queue, but how does it account for allocating that basis to 1-n wallets? Wallets do not have any inherent ordering. They just exist. A method is needed to determine the ordering of wallets, right?
Justin provides examples such as:
- "Lowest cost basis to highest balance"
- "Oldest tax lots to highest balance"
- "Oldest tax lots to least active wallet"
- "Highest cost basis to lowest balance"
Can you shed some light on how this will be supported with RP2?
My understanding is that, in order to be in compliance, it will be necessary to *individually document this before the end of 2024. At at this moment I'm not clear on what allocation method RP2 will be using. (I can't just say FIFO, because that doesn't cover wallet selection.)
Thoughts? Thanks!
Thanks for the additional feedback.
The current RP2 per-wallet design uses a combination of transfers (IntraTransactions) plus the new concept of transfer semantics (which can be FIFO, LIFO, etc.) to determine where funds go. So the "Global Allocation Method" in the current version of the design would be something like: funds are directed to wallets based on transfers and within each transfer the cost basis is chosen using transfer semantics.
However choosing "Specific Allocation Method" might also work, because RP2 tracks lot pairings precisely, so when the time comes to file taxes the RP2 full report will describe in detail how cost basis has been allocated (my understanding is that when picking "Specific Allocation Method" you don't have to declare the exact allocation by Dec 31st, but correct me if I'm wrong).
Again, these are just thoughts I'm giving while design and implementation are still ongoing: unfortunately this change of rules didn't give us enough time to design and implement this well in advance (also none of this is tax or financial advice). I think the best bet is to follow the advice in Andreas' video, because, after consolidation, your situation becomes agnostic to what happens with tax software.
We could also implement methods like those suggested by Justin, but unfortunately there won't be even time to finish the basic implementation by Dec 31st.
