Amir Balazade

Amir Balazade

Front-end engineer

abalazade@gmail.comlinkedin.com/in/amirbalazade

Istanbul, open to remote

Index

One ticket, three defects

The wallet PWA. A swap took the user's signature and then did nothing: no message, no navigation, no record. The ticket described one fault. The code had three.

Surface
Wallet PWA: buy, sell, transfer, swap, credit
Work
Signing-flow rewrite, error taxonomy, login failure messages
Stack
Angular, RxJS, multi-chain signing strategies

Reading past the report

A bug report names a symptom. This one said a rejection after signing became an unhandled rejection. Following it through the code found three independent faults stacked on each other, and the report named one.

The promise was never awaited. Four order-creation paths kicked off the signing flow inside an operator that discards whatever it returns. The stream completed on the HTTP response, so the rejection had nothing left to land in.

The escaped rejection was then swallowed deliberately. The global handler did receive it, and did unwrap it correctly — then returned early for anything shaped like an HTTP error. A failed submit is exactly that shape, so it produced no message and no telemetry. That early return was not swap-specific: it silenced every failed order creation in the app.

A swap signs twice, and the second leg was orphaned. When the server asked for a second signature, the second leg was started fire-and-forget from inside the first leg's stream. Leg one resolved on its own response, before leg two had even opened its dialog, and leg two logged its errors to the console and subscribed with no error handler. For a swap, that is the likeliest mechanism of taking a signature and doing nothing.

Fixing only the path the ticket described would have left the other two live, and left every non-swap flow just as silent.

What the rewrite had to guarantee

Signing is the one place in a wallet where ordinary error-handling habits are actively dangerous. The usual instincts — attach the original error as a cause, log the object for debugging, put the server's message in the toast — all end with key material or a signed payload somewhere it was never meant to be.

So the chain was built so that no strategy error object is ever retained, re-thrown, wrapped as a cause or displayed. Failures are narrowed into a fixed set of categories and the category string is the entire message; the server's text never reaches the user. Cancelling is a category too — no toast, still navigates, nothing to telemetry. The response-caching layer cannot hold a signed payload, because it declines anything that is not a GET.

The gate that caught what I missed

Work on this path goes through a dedicated review before merge. Mine came back blocked, with seven things to fix and eight to consider — the most serious being that the interface, as rewritten, still let the caller drive repeated signing without asking again.

That is the review working. The containment design held up and the confidentiality rules were met, and the sequencing was still wrong in a way that only a reader looking specifically for it would catch. Defects found outside the scope of the change went into a separate file rather than into the review, so the review stayed about the diff and the rest still got recorded.

While we were in there: login errors

The same area had a smaller, related problem. A failed login produced two generic toasts in succession, and every cause produced the same pair — wrong phrase, wrong passcode, rejected account, all indistinguishable. The phrase step validated only that twelve non-empty words had been typed.

Each cause now says what happened, and says it once.

Login screen showing a generic failure toast
Before: one message for every cause.
Login screen stating that the secret phrase entered is invalid
After: the phrase is checked, and the message names what was wrong. Test vector words, not anyone's wallet.

Zhambon, where the interesting part is the deployment