Conversation
dangeross
commented
Mar 13, 2026
- Runtime state switching - Stable balance activates/deactivates via user settings in real time without restart
- Event middleware - Adds middleware that can mutate/react/suppress events. Used to:
- React and inject a conversion status to a succeeded payment, before queuing a per-receive conversion
- Suppress payment events for conversion child payments (the internal send/receive plumbing), so external listeners only see the parent payment events
- Per-receive conversions - Each incoming payment is individually converted to the stable token on receipt, using deterministic transfer IDs for idempotency
- Reserved removed - Auto-convert now converts the full sats balance above threshold instead of holding back reserved_sats (with token dust checks)
- Dynamic min limit adjustment - Sends ToBitcoin automatically raise the amount to meet the min conversion limit when trying to send lower amounts, and convert the full token balance to avoid stranded token dust
- Token dust prevention - Auto-convert skips if converting would leave a token balance below the ToBitcoin min limit
c0d197d to
d3f1d5a
Compare
d3f1d5a to
cb7ed48
Compare
danielgranhao
left a comment
There was a problem hiding this comment.
Haven't finished reviewing but leaving here a few comments already :)
roeierez
left a comment
There was a problem hiding this comment.
This is huge! I've done a first pass, but I'll definitely need to go through it again to fully wrap my head around it.
|
|
||
| // Payment succeeded → check if it resolves a deferred conversion, | ||
| // then queue per-receive or auto-convert as needed | ||
| SdkEvent::PaymentSucceeded { mut payment } => { |
There was a problem hiding this comment.
I think we have a behavior currently that PaymentSucceeded is emitted also for past payments during sync. Just making sure it is not an issue here.
Another thing is that I am not sure at all we should emit Payment events for past payments and I also want to make sure that when we change this behavior it will not affect this logic as well.
There was a problem hiding this comment.
It only starts emitting PaymentSucceeded after the initial sync is complete
There was a problem hiding this comment.
Right. What about instance that has passed the initial sync and was restarted after a while? It willreceive these past events won't it?
There was a problem hiding this comment.
The initial sync is a watcher, its only triggered on the first sync after the instance is started. Payment events arn't emitted before that
|
|
||
| // Check if a send-with-conversion is in progress on another instance | ||
| if let Some(client) = &self.signing_client { | ||
| match client.get_lock(PAYMENT_LOCK_NAME).await { |
There was a problem hiding this comment.
Now that you came up with the deterministic transfer id for conversion I wonder if we can use idempotency to skip the distributed lock in this case.
There was a problem hiding this comment.
Its more of a belt-and-suspenders approach with redundant checking. We check:
- The lock
- The deterministic payment id is not is storage
- and, in case of lag, the idempotency is the catch all
Do you think its too much?
There was a problem hiding this comment.
I only think that if we can achieve the same result without the distributed lock then it is definitely better.
Do you see any issue if we count only on the idempotency? It seems that the first one to create the transfer wins and handle the conversion and the second one fails (probay need to handle it gracefully). Is that right or perhaps I am missing something?
6aaae1e to
962f7c8
Compare
68d4559 to
1dc08b8
Compare
33e4d59 to
5af3e61
Compare
aeb19db to
a8447f8
Compare
a8447f8 to
efc6691
Compare