11import React , { useState } from 'react'
22import { useStore } from '../../lib/store'
33import { signTransactionWithFreighter } from '../../lib/wallet/freighter'
4+ import { signXdrWithLedger , isLedgerSupported , getActiveLedgerSession } from '../../lib/wallet/ledger'
5+ import { NETWORKS } from '../../lib/stellar'
46import Card from './Card'
57
68export default function TransactionSigner ( ) {
@@ -10,6 +12,9 @@ export default function TransactionSigner() {
1012 const [ signing , setSigning ] = useState ( false )
1113 const [ error , setError ] = useState ( null )
1214 const [ copied , setCopied ] = useState ( false )
15+ const [ ledgerPrompt , setLedgerPrompt ] = useState ( false )
16+
17+ const networkPassphrase = NETWORKS [ network ] ?. passphrase || NETWORKS . testnet . passphrase
1318
1419 const handleSign = async ( ) => {
1520 if ( ! xdr . trim ( ) ) {
@@ -23,18 +28,15 @@ export default function TransactionSigner() {
2328
2429 try {
2530 let result = null
26- const networkName = network === 'mainnet' ? 'PUBLIC' : 'TESTNET'
2731
2832 if ( walletType === 'freighter' ) {
33+ const networkName = network === 'mainnet' ? 'PUBLIC' : 'TESTNET'
2934 result = await signTransactionWithFreighter ( xdr . trim ( ) , networkName )
3035 } else if ( walletType === 'ledger' ) {
31- setError ( 'Ledger signing requires the device to be connected. Use the Builder tab to build and sign transactions.' )
32- setSigning ( false )
33- return
36+ await _signWithLedger ( )
37+ return // _signWithLedger manages its own state
3438 } else {
35- setError ( 'No wallet connected. Connect a wallet first.' )
36- setSigning ( false )
37- return
39+ throw new Error ( 'No wallet connected. Connect a wallet first.' )
3840 }
3941
4042 setSignedXdr ( result )
@@ -45,6 +47,47 @@ export default function TransactionSigner() {
4547 }
4648 }
4749
50+ const _signWithLedger = async ( ) => {
51+ // Check browser support first
52+ const supported = await isLedgerSupported ( )
53+ if ( ! supported ) {
54+ setError (
55+ 'WebUSB/WebHID is not supported in this browser. ' +
56+ 'Please use Chrome or a Chromium-based browser to sign with Ledger.'
57+ )
58+ setSigning ( false )
59+ return
60+ }
61+
62+ // If we already have a live stellarApp session from WalletConnect, use it.
63+ // Otherwise, prompt the user to reconnect via the Wallet tab.
64+ const { stellarApp, publicKey } = getActiveLedgerSession ( )
65+ if ( ! stellarApp ) {
66+ setError (
67+ 'Ledger session not found. Please connect your Ledger in the Wallet tab first, ' +
68+ 'then return here to sign.'
69+ )
70+ setSigning ( false )
71+ return
72+ }
73+
74+ try {
75+ setLedgerPrompt ( true )
76+ const signed = await signXdrWithLedger (
77+ xdr . trim ( ) ,
78+ networkPassphrase ,
79+ stellarApp ,
80+ publicKey || walletPublicKey
81+ )
82+ setSignedXdr ( signed )
83+ } catch ( err ) {
84+ setError ( err . message )
85+ } finally {
86+ setLedgerPrompt ( false )
87+ setSigning ( false )
88+ }
89+ }
90+
4891 const handleCopy = ( ) => {
4992 if ( signedXdr ) {
5093 navigator . clipboard . writeText ( signedXdr )
@@ -85,17 +128,37 @@ export default function TransactionSigner() {
85128 < span style = { { fontFamily : 'var(--font-mono)' } } >
86129 { walletPublicKey ?. slice ( 0 , 8 ) } …{ walletPublicKey ?. slice ( - 8 ) }
87130 </ span >
131+ < span style = { { marginLeft : 'auto' , opacity : 0.7 } } > { walletType } </ span >
88132 </ div >
89133
134+ { /* Ledger device prompt banner */ }
135+ { ledgerPrompt && (
136+ < div style = { {
137+ padding : '12px' ,
138+ background : 'var(--amber-glow, rgba(245,158,11,0.1))' ,
139+ border : '1px solid var(--amber, #f59e0b)' ,
140+ borderRadius : 'var(--radius-md)' ,
141+ fontSize : '12px' ,
142+ color : 'var(--amber, #f59e0b)' ,
143+ display : 'flex' , alignItems : 'center' , gap : '8px' ,
144+ } } >
145+ < span style = { { fontSize : '18px' } } > 🔐</ span >
146+ Review and confirm the transaction on your Ledger device…
147+ </ div >
148+ ) }
149+
90150 { /* XDR input */ }
91151 < div >
92- < label style = { { fontSize : '10px' , color : 'var(--text-muted)' , letterSpacing : '1px' , textTransform : 'uppercase' , display : 'block' , marginBottom : '6px' } } >
152+ < label style = { {
153+ fontSize : '10px' , color : 'var(--text-muted)' , letterSpacing : '1px' ,
154+ textTransform : 'uppercase' , display : 'block' , marginBottom : '6px' ,
155+ } } >
93156 TRANSACTION XDR
94157 </ label >
95158 < textarea
96159 value = { xdr }
97160 onChange = { ( e ) => setXdr ( e . target . value ) }
98- placeholder = "Paste the transaction XDR envelope here... "
161+ placeholder = "Paste the unsigned transaction XDR envelope here… "
99162 rows = { 5 }
100163 style = { {
101164 width : '100%' ,
@@ -135,7 +198,7 @@ export default function TransactionSigner() {
135198 { signing ? (
136199 < >
137200 < div className = "spinner" />
138- Signing…
201+ { ledgerPrompt ? 'Waiting for Ledger…' : ' Signing…' }
139202 </ >
140203 ) : (
141204 'Sign Transaction'
0 commit comments