Skip to content

Commit 704b448

Browse files
warmidrisclaude
andcommitted
Slim down to version pin + wallet cancellation detection only
Remove over-engineered Leather compatibility layer (signature normalization, ClarityValue-to-JSON conversion, 3-tier fallback signing). Keep only the @stacks/connect@8.2.5 version pin and wallet cancellation error handling around the original direct request() calls. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent c03c527 commit 704b448

File tree

2 files changed

+29
-238
lines changed

2 files changed

+29
-238
lines changed

docs/app.js

Lines changed: 15 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -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(/^0x/, "").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

573516
function 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-
659533
async 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

server/ui/main.src.js

Lines changed: 14 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -929,54 +929,7 @@ function extractTxid(response) {
929929
return null;
930930
}
931931

932-
// ── Leather wallet compatibility ────────────────────────────────────────────
933-
934-
function normalizeSip018SignatureForClarity(signature) {
935-
const raw = (signature ?? "").replace(/^0x/, "").toLowerCase();
936-
if (raw.length !== 130) return signature;
937-
const firstByte = parseInt(raw.slice(0, 2), 16);
938-
const lastByte = parseInt(raw.slice(128, 130), 16);
939-
const isRecoveryId = (v) => v === 0 || v === 1 || v === 27 || v === 28;
940-
if (isRecoveryId(firstByte) && !isRecoveryId(lastByte)) {
941-
return `0x${raw.slice(2)}${raw.slice(0, 2)}`;
942-
}
943-
return signature;
944-
}
945-
946-
function cvToWalletJson(cv) {
947-
if (!cv || typeof cv !== "object") return cv;
948-
switch (cv.type) {
949-
case "int": return { type: "int", value: String(cv.value) };
950-
case "uint": return { type: "uint", value: String(cv.value) };
951-
case "address":
952-
case "contract": return { type: "principal", value: cv.value };
953-
case "ascii": return { type: "string-ascii", value: cv.value };
954-
case "utf8": return { type: "string-utf8", value: cv.value };
955-
case "none": return { type: "none" };
956-
case "some": return { type: "some", value: cvToWalletJson(cv.value) };
957-
case "ok": return { type: "ok", value: cvToWalletJson(cv.value) };
958-
case "err": return { type: "err", value: cvToWalletJson(cv.value) };
959-
case "buffer": return { type: "buffer", data: cv.value };
960-
case "true": return { type: "bool", value: true };
961-
case "false": return { type: "bool", value: false };
962-
case "tuple": {
963-
const data = {};
964-
for (const [k, v] of Object.entries(cv.value || {})) {
965-
data[k] = cvToWalletJson(v);
966-
}
967-
return { type: "tuple", data };
968-
}
969-
case "list": {
970-
const list = (cv.value || []).map(cvToWalletJson);
971-
return { type: "list", list };
972-
}
973-
default: return cv;
974-
}
975-
}
976-
977-
function getLeatherProvider() {
978-
return window.LeatherProvider ?? null;
979-
}
932+
// ── Wallet error handling ───────────────────────────────────────────────────
980933

981934
function isWalletCancellationError(error) {
982935
const msg = (error instanceof Error ? error.message : String(error ?? "")).toLowerCase();
@@ -995,60 +948,6 @@ function normalizeWalletPromptError(error, action) {
995948
return error instanceof Error ? error : new Error(String(error ?? "Unknown wallet error"));
996949
}
997950

998-
async function signStructuredMessageWithFallback(domain, message) {
999-
let lastError = null;
1000-
1001-
try {
1002-
const response = await request("stx_signStructuredMessage", {
1003-
domain,
1004-
message,
1005-
});
1006-
const sig = extractSignature(response);
1007-
if (sig) return normalizeSip018SignatureForClarity(sig);
1008-
} catch (err) {
1009-
lastError = normalizeWalletPromptError(err, "signature");
1010-
if (isWalletCancellationError(err)) throw lastError;
1011-
}
1012-
1013-
const provider = getLeatherProvider();
1014-
if (provider) {
1015-
try {
1016-
const response = await provider.request("stx_signStructuredMessage", {
1017-
message,
1018-
domain,
1019-
});
1020-
const sig = extractSignature(response);
1021-
if (sig) return normalizeSip018SignatureForClarity(sig);
1022-
} catch (err) {
1023-
lastError = normalizeWalletPromptError(err, "signature");
1024-
if (isWalletCancellationError(err)) throw lastError;
1025-
}
1026-
1027-
try {
1028-
const response = await provider.request("stx_signStructuredMessage", {
1029-
message: cvToWalletJson(message),
1030-
domain: cvToWalletJson(domain),
1031-
});
1032-
const sig = extractSignature(response);
1033-
if (sig) return normalizeSip018SignatureForClarity(sig);
1034-
} catch (err) {
1035-
lastError = normalizeWalletPromptError(err, "signature");
1036-
if (isWalletCancellationError(err)) throw lastError;
1037-
}
1038-
}
1039-
1040-
if (lastError) {
1041-
const msg = lastError.message ?? "";
1042-
if (msg.includes("not supported") || msg.includes("structured")) {
1043-
throw new Error("Your wallet doesn't support structured data signing (SIP-018). Try Leather v6+.");
1044-
}
1045-
throw lastError;
1046-
}
1047-
throw new Error("Wallet did not return a signature");
1048-
}
1049-
1050-
// ────────────────────────────────────────────────────────────────────────────
1051-
1052951
function buildStackflowNodePayload() {
1053952
const parsed = parseSignerInputs();
1054953
const contractId = parseContractId();
@@ -1498,10 +1397,19 @@ async function signStructuredState() {
14981397
}
14991398

15001399
const state = await buildStructuredState();
1501-
const signature = await signStructuredMessageWithFallback(
1502-
state.domain,
1503-
state.message,
1504-
);
1400+
let response;
1401+
try {
1402+
response = await request("stx_signStructuredMessage", {
1403+
domain: state.domain,
1404+
message: state.message,
1405+
});
1406+
} catch (err) {
1407+
throw normalizeWalletPromptError(err, "signature");
1408+
}
1409+
const signature = extractSignature(response);
1410+
if (!signature) {
1411+
throw new Error("Wallet did not return a signature");
1412+
}
15051413

15061414
getInput(ids.sigMySignature).value = normalizeHex(
15071415
signature,

0 commit comments

Comments
 (0)