Commit 31724ca
feat(analytics): report signing for every transaction, and give each flow stage one meaning (#3008)
* feat(analytics): emit signing metrics on the hardware wallet branch
Hardware signers produced no signing.* event. `useSetupSigningFlow`
diverts them to the HardwareSign overlay and never dispatches the sign
thunk. The handlers in popup/metrics/access.ts key on that thunk, so
every hardware approval was lost. Rejections still fired, because
`rejectAndClose` dispatches the reject thunk for both key types. The
funnel therefore showed hardware rejections with no approvals.
This change adds the missing events for all three signing views:
transaction, message, and auth entry.
Add popup/metrics/signing.ts. This module owns the signing event schema.
It selects the event name, adds the constant `message_type`, derives
`origin`, and scrubs `reason_code`. Both key types emit through it, so
the two paths cannot drift apart.
Move the software-key handlers in access.ts onto the same module. The
emitted payloads do not change.
Emit from the HardwareSign overlay:
- Success fires after `handleSignedHwPayload` resolves. The software
event fires when the background resolves the dApp request, and that
call is the equivalent point. A device signature that never reaches
the dApp is not an approval.
- Failure fires on a rejected sign thunk and on any throw. This covers
no device attached, the mismatched-account refusal, and a payload
delivery failure.
- Internal send, swap, and trustline flows emit nothing. They report
their outcome as payment.completed, swap.completed, or asset.added.
Thread the dApp url from the three views into the overlay so hardware
events carry the same `origin` as software events.
A transaction runtime failure emits nothing, on both key types. The
shared catalog has no such event, and access.ts registers no
`signTransaction.rejected` handler. FAILED_EVENT records the gap
explicitly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qkq7j6uvUMU1bBQdcuGX8Z
* fix(analytics): report a device decline as a rejection, not a failure
A hardware decline is a user decision. The overlay reported every
hardware error as a signing failure, so a decline landed on
`signing.*_failed` next to real faults.
Add `isDeviceRefusalError` beside `parseWalletError`, which already
matches the same two messages. hw-app-str raises
StellarUserRefusedError("User refused the request") for the deny status
word on every sign call. Older apps and transports worded the same
decision differently, so match both.
Route a decline to `signing.*_rejected`, the event that already carries
a popup reject. A rejection carries no `reason_code`: nothing went
wrong. Every other hardware error stays on `signing.*_failed` — no
device attached, a transport fault, the wrong device, an app too old.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qkq7j6uvUMU1bBQdcuGX8Z
* feat(analytics): give each flow stage one meaning and add swap parity
The `confirm` stage marked the submitting screen, which the user only
reaches after approving. Mobile marks the review sheet, before the user
decides. The two clients therefore counted different things.
Move `confirm` to the review modal in the send and swap amount screens.
Emit it from an effect on the modal's open state, so every entry point
counts once and a reopen counts again.
Drop the submitting screen from both step maps. Its stages are already
reported by the submission-status effect (processing, then success), so
a third event would double-count and reuse a name that now belongs to
the review modal.
Add the processing and success stages to the swap flow. Swap reported
neither, so a swap could not be followed past the review screen.
Reset the stage guards on ERROR as well as IDLE. A retry goes
ERROR -> PENDING without passing through IDLE, because returning from
the failure screen does not reset the submission. Guarding on IDLE
alone dropped every retried attempt.
Bump the schema version to 4. The `confirm` stage changes meaning, so
consumers must tell an old client from a new one.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qkq7j6uvUMU1bBQdcuGX8Z
* feat(analytics): report signing for internal transactions
Internal transactions reported nothing for the signing action. Only a
dApp request did, so approvals and signing outcomes were unmeasurable
for the transactions the wallet composes itself.
Add `signing.transaction_failed`. The message and auth-entry families
each carry approved, rejected and failed. The transaction family had
no failure event, so a signing fault went unreported on every path.
Register it for the dApp thunk as well.
Add a `source` property to every signing event: `dapp_api` for a
website request, `internal` for a wallet-composed one. Both origins now
emit the same events with the same properties, so one query counts all
signing and `source` splits it. An internal transaction has no origin,
so it omits that property.
Emit from every place internal signing ends:
- the submission hook, for software keys;
- the hardware overlay, for a device. A device signs before the
submission hook runs, and a decline keeps the user on the overlay, so
the flow never reaches the hook. This is the case that reported
nothing at all;
- the review screen's cancel, which is the internal rejection;
- the trustline flow, which runs its own signing step.
Split the ownership by key type so an internal hardware signing is
reported once: the overlay owns the device attempt, the submission hook
owns the software attempt.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qkq7j6uvUMU1bBQdcuGX8Z
* fix(analytics): make signing outcomes reachable and correctly paired
The signing wrappers discarded every failure. The background answers
with an error object rather than throwing, and the wrappers ignored
both that answer and any transport exception. A failed signing
therefore resolved like a success: the approval event fired, and the
failure event was unreachable. Surface both kinds of failure, matching
the pattern the token-add wrapper already uses.
This applies to the hardware payload handover as well. That path
reported an approval even when the request never reached the website.
Report a message rejection when the user declines a message prompt. The
message view dispatched the transaction reject request, so declining a
message reported a transaction rejection and omitted the message type.
Report a rejection when the user cancels a trustline review. A
dedicated handler keeps the success and close paths out of the count,
because they reuse the same cancel callback.
Correct the failure helper's description, which still described the
transaction outcome as disabled.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qkq7j6uvUMU1bBQdcuGX8Z
* fix(analytics): cover every way a user leaves a review, and read real errors
Report a rejection when the user leaves a review by any route. The
handlers covered the Cancel buttons only, so dismissing through the
modal backdrop reported a review stage with no outcome. Each flow now
keys the rejection on the review closing, and marks an approval first
so it is not counted. Leaving the send review to edit the memo is not a
decision, so it is not counted either.
Report a trustline rejection on the same basis. That review lives in a
modal whose backdrop no button handler sees.
Ignore a submission status left behind by an earlier submission. The
status lives in the store, so a view that mounted and found a terminal
status reported a stage the user never reached. Both flows now wait
until the status has been seen idle, which the reset on mount
guarantees. The swap stage effect moves below that reset, matching the
send flow.
Read a real message out of a background error. The error is an object
on some paths, which stringified to "{}" and reached the reason code
with no information.
Keep a string error unquoted. The hardware overlay passes the message
directly on one branch, and stringifying it wrapped the reason code in
quotes.
Expect a signing failure in the integration helper. It drives the
signing wrappers with a placeholder request id, so they now reject, and
an unhandled rejection would stop the remaining checks.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qkq7j6uvUMU1bBQdcuGX8Z
* fix(analytics): latch every trustline approval, and model the signing sets as enums
Route every approval of the trustline review through one handler. The
path behind the security warning skipped the approval mark, so a
transaction approved that way reported an approval and then a
rejection.
Model the signing kind and the signing origin as enums. The repository
requires a finite named set of string values to be an enum, not a union
type.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qkq7j6uvUMU1bBQdcuGX8Z
* fix(metrics): keep the hardware error message a string
JSON.stringify returns undefined for a value it cannot represent. Fall
back to the empty string so the helper keeps its declared return type.
emitSigningFailed reports that as "unknown".
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Qkq7j6uvUMU1bBQdcuGX8Z
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 19781cc commit 31724ca
29 files changed
Lines changed: 1666 additions & 110 deletions
File tree
- extension/src
- helpers
- popup
- components
- InternalTransaction/SubmitTransaction/hooks
- __tests__
- hardwareConnect/HardwareSign
- __tests__
- manageAssets/ManageAssetRows/ChangeTrustInternal
- hooks
- send/SendAmount
- swap/SwapAmount
- __tests__
- constants
- __tests__
- helpers
- __tests__
- metrics
- __tests__
- views
- Send
- SignAuthEntry
- SignMessage
- SignTransaction
- Swap
- __tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
20 | 70 | | |
21 | 71 | | |
22 | 72 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1546 | 1546 | | |
1547 | 1547 | | |
1548 | 1548 | | |
| 1549 | + | |
| 1550 | + | |
| 1551 | + | |
| 1552 | + | |
| 1553 | + | |
| 1554 | + | |
| 1555 | + | |
| 1556 | + | |
| 1557 | + | |
| 1558 | + | |
| 1559 | + | |
| 1560 | + | |
| 1561 | + | |
| 1562 | + | |
| 1563 | + | |
| 1564 | + | |
| 1565 | + | |
| 1566 | + | |
| 1567 | + | |
| 1568 | + | |
| 1569 | + | |
| 1570 | + | |
| 1571 | + | |
| 1572 | + | |
| 1573 | + | |
| 1574 | + | |
| 1575 | + | |
1549 | 1576 | | |
1550 | 1577 | | |
1551 | 1578 | | |
| |||
1556 | 1583 | | |
1557 | 1584 | | |
1558 | 1585 | | |
1559 | | - | |
| 1586 | + | |
| 1587 | + | |
| 1588 | + | |
1560 | 1589 | | |
1561 | 1590 | | |
1562 | 1591 | | |
1563 | 1592 | | |
1564 | 1593 | | |
1565 | 1594 | | |
| 1595 | + | |
| 1596 | + | |
| 1597 | + | |
| 1598 | + | |
| 1599 | + | |
| 1600 | + | |
| 1601 | + | |
| 1602 | + | |
1566 | 1603 | | |
1567 | 1604 | | |
| 1605 | + | |
1568 | 1606 | | |
1569 | 1607 | | |
1570 | 1608 | | |
| |||
1597 | 1635 | | |
1598 | 1636 | | |
1599 | 1637 | | |
1600 | | - | |
| 1638 | + | |
| 1639 | + | |
| 1640 | + | |
1601 | 1641 | | |
1602 | 1642 | | |
1603 | 1643 | | |
1604 | 1644 | | |
| 1645 | + | |
| 1646 | + | |
| 1647 | + | |
| 1648 | + | |
| 1649 | + | |
| 1650 | + | |
| 1651 | + | |
| 1652 | + | |
1605 | 1653 | | |
1606 | 1654 | | |
| 1655 | + | |
1607 | 1656 | | |
1608 | 1657 | | |
1609 | 1658 | | |
| |||
1617 | 1666 | | |
1618 | 1667 | | |
1619 | 1668 | | |
1620 | | - | |
| 1669 | + | |
| 1670 | + | |
| 1671 | + | |
1621 | 1672 | | |
1622 | 1673 | | |
1623 | 1674 | | |
1624 | 1675 | | |
1625 | 1676 | | |
| 1677 | + | |
| 1678 | + | |
| 1679 | + | |
| 1680 | + | |
| 1681 | + | |
| 1682 | + | |
| 1683 | + | |
| 1684 | + | |
1626 | 1685 | | |
1627 | 1686 | | |
| 1687 | + | |
1628 | 1688 | | |
1629 | 1689 | | |
1630 | 1690 | | |
| |||
1636 | 1696 | | |
1637 | 1697 | | |
1638 | 1698 | | |
1639 | | - | |
| 1699 | + | |
| 1700 | + | |
| 1701 | + | |
1640 | 1702 | | |
1641 | 1703 | | |
1642 | 1704 | | |
1643 | 1705 | | |
| 1706 | + | |
| 1707 | + | |
| 1708 | + | |
| 1709 | + | |
| 1710 | + | |
| 1711 | + | |
| 1712 | + | |
| 1713 | + | |
1644 | 1714 | | |
1645 | 1715 | | |
| 1716 | + | |
1646 | 1717 | | |
1647 | 1718 | | |
1648 | 1719 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | | - | |
149 | | - | |
| 148 | + | |
| 149 | + | |
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
| |||
212 | 212 | | |
213 | 213 | | |
214 | 214 | | |
215 | | - | |
| 215 | + | |
216 | 216 | | |
217 | 217 | | |
218 | 218 | | |
| |||
524 | 524 | | |
525 | 525 | | |
526 | 526 | | |
527 | | - | |
| 527 | + | |
528 | 528 | | |
529 | 529 | | |
530 | 530 | | |
| |||
610 | 610 | | |
611 | 611 | | |
612 | 612 | | |
613 | | - | |
| 613 | + | |
614 | 614 | | |
615 | 615 | | |
616 | 616 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
123 | 123 | | |
124 | 124 | | |
125 | 125 | | |
126 | | - | |
127 | | - | |
128 | | - | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
129 | 130 | | |
130 | | - | |
| 131 | + | |
131 | 132 | | |
132 | 133 | | |
133 | 134 | | |
| |||
Lines changed: 80 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
24 | 30 | | |
25 | 31 | | |
26 | 32 | | |
| |||
30 | 36 | | |
31 | 37 | | |
32 | 38 | | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
33 | 49 | | |
34 | 50 | | |
35 | 51 | | |
| |||
205 | 221 | | |
206 | 222 | | |
207 | 223 | | |
| 224 | + | |
| 225 | + | |
208 | 226 | | |
209 | 227 | | |
210 | 228 | | |
| |||
544 | 562 | | |
545 | 563 | | |
546 | 564 | | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
547 | 627 | | |
548 | 628 | | |
549 | 629 | | |
| |||
0 commit comments