@@ -511,64 +511,7 @@ function extractTxid(response) {
511511 return null ;
512512}
513513
514- // ── Leather wallet compatibility ────────────────────────────────────────────
515-
516- /**
517- * Normalize SIP-018 signatures from VRS (Leather default) to RSV (Clarity expected).
518- * Leather and some other wallets return the recovery byte first (VRS format),
519- * but Clarity contracts and StackFlow verification expect RSV format.
520- */
521- function normalizeSip018SignatureForClarity ( signature ) {
522- const raw = ( signature ?? "" ) . replace ( / ^ 0 x / , "" ) . toLowerCase ( ) ;
523- if ( raw . length !== 130 ) return signature ;
524- const firstByte = parseInt ( raw . slice ( 0 , 2 ) , 16 ) ;
525- const lastByte = parseInt ( raw . slice ( 128 , 130 ) , 16 ) ;
526- const isRecoveryId = ( v ) => v === 0 || v === 1 || v === 27 || v === 28 ;
527- if ( isRecoveryId ( firstByte ) && ! isRecoveryId ( lastByte ) ) {
528- return `0x${ raw . slice ( 2 ) } ${ raw . slice ( 0 , 2 ) } ` ;
529- }
530- return signature ;
531- }
532-
533- /**
534- * Convert a ClarityValue to wallet-JSON format for older Leather versions
535- * that don't accept raw ClarityValue objects in stx_signStructuredMessage.
536- * Uses string-based type identifiers from @stacks/transactions v7.
537- */
538- function cvToWalletJson ( cv ) {
539- if ( ! cv || typeof cv !== "object" ) return cv ;
540- switch ( cv . type ) {
541- case "int" : return { type : "int" , value : String ( cv . value ) } ;
542- case "uint" : return { type : "uint" , value : String ( cv . value ) } ;
543- case "address" :
544- case "contract" : return { type : "principal" , value : cv . value } ;
545- case "ascii" : return { type : "string-ascii" , value : cv . value } ;
546- case "utf8" : return { type : "string-utf8" , value : cv . value } ;
547- case "none" : return { type : "none" } ;
548- case "some" : return { type : "some" , value : cvToWalletJson ( cv . value ) } ;
549- case "ok" : return { type : "ok" , value : cvToWalletJson ( cv . value ) } ;
550- case "err" : return { type : "err" , value : cvToWalletJson ( cv . value ) } ;
551- case "buffer" : return { type : "buffer" , data : cv . value } ;
552- case "true" : return { type : "bool" , value : true } ;
553- case "false" : return { type : "bool" , value : false } ;
554- case "tuple" : {
555- const data = { } ;
556- for ( const [ k , v ] of Object . entries ( cv . value || { } ) ) {
557- data [ k ] = cvToWalletJson ( v ) ;
558- }
559- return { type : "tuple" , data } ;
560- }
561- case "list" : {
562- const list = ( cv . value || [ ] ) . map ( cvToWalletJson ) ;
563- return { type : "list" , list } ;
564- }
565- default : return cv ;
566- }
567- }
568-
569- function getLeatherProvider ( ) {
570- return window . LeatherProvider ?? null ;
571- }
514+ // ── Wallet error handling ───────────────────────────────────────────────────
572515
573516function isWalletCancellationError ( error ) {
574517 const msg = ( error instanceof Error ? error . message : String ( error ?? "" ) ) . toLowerCase ( ) ;
@@ -587,75 +530,6 @@ function normalizeWalletPromptError(error, action) {
587530 return error instanceof Error ? error : new Error ( String ( error ?? "Unknown wallet error" ) ) ;
588531}
589532
590- /**
591- * Sign a SIP-018 structured message with 3-tier fallback:
592- * 1. @stacks/connect request() — standard path, works with modern wallets
593- * 2. Direct provider.request() with ClarityValue objects
594- * 3. Direct provider.request() with wallet-JSON format (older Leather)
595- *
596- * Returns the normalized RSV signature.
597- */
598- async function signStructuredMessageWithFallback ( domain , message , network ) {
599- let lastError = null ;
600-
601- // Path 1: @stacks/connect request()
602- try {
603- const response = await request ( "stx_signStructuredMessage" , {
604- network,
605- domain,
606- message,
607- } ) ;
608- const sig = extractSignature ( response ) ;
609- if ( sig ) return normalizeSip018SignatureForClarity ( sig ) ;
610- } catch ( err ) {
611- lastError = normalizeWalletPromptError ( err , "signature" ) ;
612- // If the user explicitly cancelled, don't try fallbacks
613- if ( isWalletCancellationError ( err ) ) throw lastError ;
614- }
615-
616- // Path 2: Direct provider with ClarityValue objects
617- const provider = getLeatherProvider ( ) ;
618- if ( provider ) {
619- try {
620- const response = await provider . request ( "stx_signStructuredMessage" , {
621- network,
622- message,
623- domain,
624- } ) ;
625- const sig = extractSignature ( response ) ;
626- if ( sig ) return normalizeSip018SignatureForClarity ( sig ) ;
627- } catch ( err ) {
628- lastError = normalizeWalletPromptError ( err , "signature" ) ;
629- if ( isWalletCancellationError ( err ) ) throw lastError ;
630- }
631-
632- // Path 3: Wallet-JSON format for older providers
633- try {
634- const response = await provider . request ( "stx_signStructuredMessage" , {
635- network,
636- message : cvToWalletJson ( message ) ,
637- domain : cvToWalletJson ( domain ) ,
638- } ) ;
639- const sig = extractSignature ( response ) ;
640- if ( sig ) return normalizeSip018SignatureForClarity ( sig ) ;
641- } catch ( err ) {
642- lastError = normalizeWalletPromptError ( err , "signature" ) ;
643- if ( isWalletCancellationError ( err ) ) throw lastError ;
644- }
645- }
646-
647- if ( lastError ) {
648- const msg = lastError . message ?? "" ;
649- if ( msg . includes ( "not supported" ) || msg . includes ( "structured" ) ) {
650- throw new Error ( "Your wallet doesn't support structured data signing (SIP-018). Try Leather v6+." ) ;
651- }
652- throw lastError ;
653- }
654- throw new Error ( "Wallet did not return a signature" ) ;
655- }
656-
657- // ────────────────────────────────────────────────────────────────────────────
658-
659533async function ensureWallet ( { interactive } ) {
660534 if ( state . connectedAddress ) {
661535 return state . connectedAddress ;
@@ -1138,11 +1012,20 @@ async function handleSignTransfer() {
11381012 try {
11391013 await ensureWallet ( { interactive : true } ) ;
11401014 const context = await buildTransferContext ( ) ;
1141- const signature = await signStructuredMessageWithFallback (
1142- context . domain ,
1143- context . message ,
1144- readNetwork ( ) ,
1145- ) ;
1015+ let response ;
1016+ try {
1017+ response = await request ( "stx_signStructuredMessage" , {
1018+ network : readNetwork ( ) ,
1019+ domain : context . domain ,
1020+ message : context . message ,
1021+ } ) ;
1022+ } catch ( err ) {
1023+ throw normalizeWalletPromptError ( err , "signature" ) ;
1024+ }
1025+ const signature = extractSignature ( response ) ;
1026+ if ( ! signature ) {
1027+ throw new Error ( "Wallet did not return a signature" ) ;
1028+ }
11461029 state . lastSignature = signature ;
11471030 elements . mySignature . value = signature ;
11481031
0 commit comments