Skip to content

Add TRON Phase 3: USB HID Ledger signing + broadcast - #25

Merged
szhygulin merged 3 commits into
mainfrom
feat/tron-phase3-signing
Apr 14, 2026
Merged

Add TRON Phase 3: USB HID Ledger signing + broadcast#25
szhygulin merged 3 commits into
mainfrom
feat/tron-phase3-signing

Conversation

@szhygulin

Copy link
Copy Markdown
Collaborator

Summary

  • Adds end-to-end TRON signing via @ledgerhq/hw-app-trx over USB HID. All seven prepare_tron_* tools are no longer preview-only — their handles are now accepted by send_transaction.
  • New pair_ledger_tron tool probes the USB-connected Ledger, caches the address for get_ledger_status, and verifies the TRON app is open. Pair once per session.
  • send_transaction routes by handle store: TRON handles sign on USB and broadcast via TronGrid; EVM handles continue through WalletConnect unchanged. Handles stay alive on sign/broadcast failure so users can retry within the 15-min TTL.

Why USB instead of WalletConnect

Ledger Live's WalletConnect relay does not honor the tron: CAIP namespace (verified 2026-04-14). USB HID is the only path today — the user's Ledger must be plugged into the host running the MCP, unlocked, with the Tron app open. On Linux this needs Ledger's udev rules and libudev-dev + a C toolchain for the node-hid native compile; README calls both out.

Implementation notes

  • Ledger SDK's ESM build has broken relative imports (missing .js extensions under --moduleResolution bundler). Worked around with a thin tron-usb-loader.ts using createRequire to pull the CJS entries. Isolating the require() also makes the signer trivially mockable in tests.
  • Fresh transport open per operation, closed in finally — HID handles are exclusive, so holding one would block ledger-live and other tooling.
  • Device address is re-verified against the prepared tx's from on every sign call; cached pairing is never trusted for authorization.
  • Empty tokenSignatures [] passed to signTransaction — USDT has hardcoded TRON-app support; other TRC-20 amounts may display as raw hex. Contract address + amount remain verifiable on-device.
  • TronGrid error messages come back hex-encoded; broadcastTronTx decodes to UTF-8 before surfacing.

Test plan

  • 13 new tests in test/tron-phase3-signing.test.ts covering: happy-path sign, address-mismatch refusal, user-reject (0x6985), wrong-app (0x6511), no-device, malformed signature, broadcast happy path, hex-decoded error, plain-text error, send_transaction TRON routing with handle retirement, handle survives sign failure, pair_ledger_tron populates session status.
  • Full suite: 321/321 pass.
  • tsc clean.
  • End-to-end smoke on a physical Ledger (reviewer).

🤖 Generated with Claude Code

Replaces the Phase-2 preview-only behavior on all `prepare_tron_*` tools
with end-to-end signing via `@ledgerhq/hw-app-trx` over USB HID, since
Ledger Live's WalletConnect relay does not honor the `tron:` namespace
(verified 2026-04-14). Adds `pair_ledger_tron`, routes TRON handles
through `send_transaction`, and broadcasts signed envelopes via
TronGrid. Handles stay alive on sign/broadcast failure so users can
retry without re-preparing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@szhygulin
szhygulin force-pushed the feat/tron-phase3-signing branch from 663a81f to 0b36638 Compare April 14, 2026 07:57
szhygulin and others added 2 commits April 14, 2026 11:15
The `getSessionStatus after pairing` test's `vi.doMock` of
walletconnect.js never applied: by the time this test ran, earlier
tests had already resolved session.ts's transitive import of the real
module, so the cached binding was used. Call vi.resetModules() before
the doMock so the fresh session.ts import picks up the stub.

Also ignore the two .mcpregistry_* token files that mcp-publisher
drops into the working tree on `login`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds an optional `accountIndex` param (default 0) to pair_ledger_tron so
users with multiple TRON accounts on a single Ledger can pair each one
by its standard Ledger Live BIP-44 slot (m/44'/195'/<i>'/0/0). The
single-slot cache is replaced with a per-path map; get_ledger_status'
`tron` field becomes an array ordered by accountIndex. send_transaction
for TRON now looks up the paired entry by `from` and signs on the
matching path, so an account-1 tx is signed with the account-1 path
instead of silently falling back to account 0 and failing the device
address check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@szhygulin
szhygulin merged commit 2880439 into main Apr 14, 2026
2 checks passed
@szhygulin
szhygulin deleted the feat/tron-phase3-signing branch April 25, 2026 02:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant