User wants to pay with ecash so swaps Token to have the right Proofs available
Mint responds with the swapped Token - the newly minted ecash: Some is P2PK locked to payment Recipient, some remains in User's possession
We fail to publish new Token to Relays for any reason
We run another Mint operation like 'Checking Proofs' or 'Swap' or 'Melt'. This will cause NDK to use the last Token event that was published successfully WHILE ignoring User's actual money that could NOT be synchronized with relays
The Mint will not allow this operation because those outdated Proofs are already spent so reponds with 400-bad request "Token already spent" message
On failing, NDK will then run 'Check Proofs' on these outdated Proofs and Mint will return 'Spent' reponse on most Proofs. Still we can have some unspent Proofs at this point but we have NEW unspent Proofs NOT included in this validity check
NDK gets rid of spent Proofs but it also publishes an OUTDATED Token state. This is where ALL newly minted BUT NOT SYNCED MONEY COULD GET LOST
This is because Mint operations are atomic while nostr operations are NOT. So payment is actually NOT an atomic operation with NIP60 wallets.
To tackle this we need workarounds:
Alert User of the Token publish failure
Save unspent money in Local Storage as well as an exported file(JSON, tags encrypted)
On the Toast message where we alert User we have a 'Retry' button that will try again to publish(uses memory)
Have ways for the User to recover the ecash on wallet page from file OR try from Local storage. We cross-examine Proofs with current wallet state, 'Check Proof validity' of new unique Proofs and try to publish new Token event. We delete successfully published Proofs from local storage
Additionally, when we run 'Check valid Proofs' operation with the Mints OR we are setting up the User's wallet, we could load unspent Proofs and try rollover again from Local storage AUTOMATICALLY too. UX improvement
The goal is to keep ecash in persistent storage besides relays AND have multiple ways to REsync ecash automatically and/or manually
Backup methods (Token event with encrypted tags):
Persistent storage: Local storage ( later IndexDB?)
File: Exported JSON file (when user logs out or clear cache it ecash can disappear)
AUTOMATIC Backup Triggers in PERSISTENT STORAGE AND MEMORY(svelte store):
On EVERY Swap and Deposit operation
Manual FILE Backup Triggers:
Button on Wallet page
On FAILED Proof rollOver operation (Toast with action btn)
Resync operation:
Either: Look for event in Memory(svelte store) OR persistent storage OR file OR a new unseen Token event was fetched. This depends on context
Cross-check with existing latest wallet state. Find new unique Proofs and add them to latest Token event
IF new Proofs were added, Try rollover operation
MANUAL Resync attempt triggers:
"Recover ecash from File" option on Wallet page
AUTOMATIC Resync attempt triggers:
On Wallet load (from persistent storage)
On FAILED Swap / Melt operation (from memory)
On NEW Token event with unspent but yet-unseen Proofs
More about outdated Tokens:
Whenever a Mint operation fails with "already spent" message (see e.g. catch block in ndk.executePayment()) we CAN let NDK run 'Check valid Proofs' AND roll over proofs
User is able to perform Mint operation now. We can Toast user to retry if Mint operation went through on second try
This way the user might see a lower than expected balance in the wallet (money gone) temporarily
BUT WE ALSO automatically launch a search for unspent ecash in parallel (in this order):
First try to fetch Memory (svelte store)
Secondly Persistent storage
Lastly Relays: Fetch Token events from as many relays in pools as possible
If we find the "lost" money we Resync AGAIN immediately
This way:
The money is very hard to lose
Payments and Withdrawals can be retried with increased chance of success
We have automatic and manual ecash recovery options available from multiple locations in the app
The scenario
This is because Mint operations are atomic while nostr operations are NOT. So payment is actually NOT an atomic operation with NIP60 wallets.
To tackle this we need workarounds: