@1sat/permission-module-ui 0.0.44 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2,68 +2,9 @@
2
2
 
3
3
  // src/OneSatPermissionPrompt.tsx
4
4
  import {
5
+ isTransactionPrompt,
5
6
  verifyIntent
6
7
  } from "@1sat/permission-module";
7
-
8
- // ../types/dist/constants.js
9
- var ORDINALS_BASKET = "p 1sat ordinals";
10
- var BSV21_BASKET = "p 1sat bsv21";
11
- var OPNS_BASKET = "p 1sat opns";
12
- var LOCK_BASKET = "p 1sat lock";
13
- var P1SAT_INTENTS = {
14
- OPNS_REGISTER: "opns.register",
15
- OPNS_DEREGISTER: "opns.deregister",
16
- OPNS_LIST: "opns.list",
17
- OPNS_TRANSFER: "opns.transfer",
18
- OPNS_CANCEL_LISTING: "opns.cancel-listing",
19
- OPNS_PURCHASE: "opns.purchase",
20
- ORDINAL_TRANSFER: "ordinal.transfer",
21
- ORDINAL_LIST: "ordinal.list",
22
- ORDINAL_CANCEL_LISTING: "ordinal.cancel-listing",
23
- ORDINAL_PURCHASE: "ordinal.purchase",
24
- ORDINAL_BURN: "ordinal.burn",
25
- ORDINAL_INSCRIBE: "ordinal.inscribe",
26
- ORDINAL_INSCRIBE_SIGMA: "ordinal.inscribe-sigma",
27
- ORDINAL_MINT_COLLECTION: "ordinal.mint-collection",
28
- ORDINAL_MINT_ITEM: "ordinal.mint-item",
29
- LOCK_LOCK: "lock.lock",
30
- LOCK_UNLOCK: "lock.unlock",
31
- BSV21_TRANSFER: "bsv21.transfer",
32
- BSV21_PURCHASE: "bsv21.purchase",
33
- BSV21_MINT: "bsv21.mint",
34
- BSV21_DEPLOY: "bsv21.deploy"
35
- };
36
- var P1SAT_INTENT_IDS = new Set(Object.values(P1SAT_INTENTS));
37
- var MAX_INSCRIPTION_BYTES = 50 * 1024 * 1024;
38
- var EXCHANGE_RATE_CACHE_TTL = 5 * 60 * 1000;
39
- var DEFAULT_STREAM_CHUNK_SIZE = 1024 * 1024;
40
-
41
- // ../types/dist/index.js
42
- var TokenType;
43
- (function(TokenType2) {
44
- TokenType2["BSV20"] = "bsv20";
45
- TokenType2["BSV21"] = "bsv21";
46
- })(TokenType || (TokenType = {}));
47
- var TokenSelectionStrategy;
48
- (function(TokenSelectionStrategy2) {
49
- TokenSelectionStrategy2["SmallestFirst"] = "smallest";
50
- TokenSelectionStrategy2["LargestFirst"] = "largest";
51
- TokenSelectionStrategy2["RetainOrder"] = "retain";
52
- TokenSelectionStrategy2["Random"] = "random";
53
- })(TokenSelectionStrategy || (TokenSelectionStrategy = {}));
54
- var TokenInputMode;
55
- (function(TokenInputMode2) {
56
- TokenInputMode2["All"] = "all";
57
- TokenInputMode2["Needed"] = "needed";
58
- })(TokenInputMode || (TokenInputMode = {}));
59
- var RoytaltyType;
60
- (function(RoytaltyType2) {
61
- RoytaltyType2["Paymail"] = "paymail";
62
- RoytaltyType2["Address"] = "address";
63
- RoytaltyType2["Script"] = "script";
64
- })(RoytaltyType || (RoytaltyType = {}));
65
-
66
- // src/OneSatPermissionPrompt.tsx
67
8
  import { useEffect, useState } from "react";
68
9
 
69
10
  // src/styles.ts
@@ -190,6 +131,28 @@ var promptStyles = `
190
131
  padding: 16px;
191
132
  }
192
133
 
134
+ /* Subtle danger chrome for burn / destructive token ops */
135
+ .opp-featured-danger {
136
+ border-color: rgba(220, 38, 38, 0.45);
137
+ background: color-mix(in srgb, var(--opp-card-bg) 88%, #7f1d1d 12%);
138
+ }
139
+
140
+ .opp-featured-danger .opp-featured-title {
141
+ color: #f87171;
142
+ }
143
+
144
+ .opp-featured-danger .opp-featured-image,
145
+ .opp-featured-danger .opp-featured-placeholder {
146
+ border: 1px solid rgba(220, 38, 38, 0.35);
147
+ background: rgba(127, 29, 29, 0.25);
148
+ color: #fca5a5;
149
+ }
150
+
151
+ .opp-dark .opp-featured-danger {
152
+ border-color: rgba(248, 113, 113, 0.4);
153
+ background: color-mix(in srgb, var(--opp-card-bg) 85%, #450a0a 15%);
154
+ }
155
+
193
156
  .opp-featured-image {
194
157
  width: 80px;
195
158
  height: 80px;
@@ -213,6 +176,89 @@ var promptStyles = `
213
176
  font-size: 18px;
214
177
  }
215
178
 
179
+ .opp-preview-opns {
180
+ display: flex;
181
+ flex-direction: column;
182
+ align-items: center;
183
+ justify-content: center;
184
+ gap: 4px;
185
+ padding: 6px;
186
+ box-sizing: border-box;
187
+ overflow: hidden;
188
+ }
189
+
190
+ .opp-preview-opns-avatar {
191
+ width: 36px;
192
+ height: 36px;
193
+ border-radius: 999px;
194
+ object-fit: cover;
195
+ background: rgba(0, 0, 0, 0.08);
196
+ flex-shrink: 0;
197
+ }
198
+
199
+ .opp-preview-opns-name {
200
+ font-size: 11px;
201
+ font-weight: 600;
202
+ max-width: 100%;
203
+ overflow: hidden;
204
+ text-overflow: ellipsis;
205
+ white-space: nowrap;
206
+ text-align: center;
207
+ color: var(--opp-fg, inherit);
208
+ }
209
+
210
+ .opp-preview-text {
211
+ margin: 0;
212
+ padding: 6px;
213
+ box-sizing: border-box;
214
+ overflow: hidden;
215
+ font-size: 9px;
216
+ line-height: 1.25;
217
+ white-space: pre-wrap;
218
+ word-break: break-word;
219
+ font-family: 'Roboto Mono', ui-monospace, SFMono-Regular, monospace;
220
+ color: var(--opp-muted);
221
+ }
222
+
223
+ .opp-preview-json {
224
+ white-space: pre;
225
+ overflow: auto;
226
+ }
227
+
228
+ .opp-preview-frame {
229
+ border: 0;
230
+ padding: 0;
231
+ overflow: hidden;
232
+ background: #fff;
233
+ pointer-events: none;
234
+ }
235
+
236
+ .opp-preview-value {
237
+ display: flex;
238
+ align-items: center;
239
+ justify-content: center;
240
+ box-sizing: border-box;
241
+ background: rgba(229, 169, 32, 0.12);
242
+ border: 1px solid rgba(229, 169, 32, 0.35);
243
+ color: var(--opp-fg, inherit);
244
+ }
245
+
246
+ .opp-preview-value-icon {
247
+ width: 32px;
248
+ height: 32px;
249
+ color: #E5A920;
250
+ }
251
+
252
+ /* Amount subtitles must never ellipsis — full sat counts are the point. */
253
+ .opp-featured-subtitle-text.opp-amount {
254
+ font-family: 'Roboto Mono', ui-monospace, SFMono-Regular, monospace;
255
+ font-variant-numeric: tabular-nums;
256
+ white-space: normal;
257
+ overflow: visible;
258
+ text-overflow: unset;
259
+ word-break: keep-all;
260
+ }
261
+
216
262
  .opp-featured-meta {
217
263
  display: flex;
218
264
  flex-direction: column;
@@ -246,6 +292,38 @@ var promptStyles = `
246
292
  font-family: 'Roboto Mono', ui-monospace, SFMono-Regular, monospace;
247
293
  }
248
294
 
295
+ .opp-featured-meta-line {
296
+ display: flex;
297
+ align-items: center;
298
+ gap: 6px;
299
+ min-width: 0;
300
+ font-size: 12px;
301
+ color: var(--opp-muted);
302
+ }
303
+
304
+ .opp-featured-meta-line-bare {
305
+ align-items: center;
306
+ }
307
+
308
+ .opp-featured-meta-key {
309
+ flex-shrink: 0;
310
+ opacity: 0.75;
311
+ min-width: 2.5em;
312
+ }
313
+
314
+ .opp-featured-meta-value {
315
+ white-space: nowrap;
316
+ overflow: hidden;
317
+ text-overflow: ellipsis;
318
+ min-width: 0;
319
+ flex: 1;
320
+ }
321
+
322
+ .opp-featured-meta-value-mono {
323
+ font-family: 'Roboto Mono', ui-monospace, SFMono-Regular, monospace;
324
+ font-size: 11px;
325
+ }
326
+
249
327
  .opp-trust-wrap {
250
328
  display: flex;
251
329
  flex-direction: column;
@@ -405,26 +483,25 @@ var promptStyles = `
405
483
  `;
406
484
 
407
485
  // src/OneSatPermissionPrompt.tsx
408
- import { jsx, jsxs, Fragment } from "react/jsx-runtime";
486
+ import { jsx, jsxs } from "react/jsx-runtime";
409
487
  function OneSatPermissionPrompt({
410
488
  request,
411
489
  onApprove,
412
490
  onReject,
413
491
  theme = "auto",
414
- services: services2
492
+ services
415
493
  }) {
416
494
  const resolvedTheme = useResolvedTheme(theme);
417
- const base = summarizeRequest(request);
495
+ const base = viewFromRequest(request);
418
496
  const [busy, setBusy] = useState(false);
419
- const [verified, setVerified] = useState(undefined);
420
- const intent = request.intent;
421
- const trustState = intent?.trust?.state;
422
- const p1satIntent = intent?.p1satIntent;
497
+ const [verified, setVerified] = useState();
498
+ const txPrompt = request.kind === "transaction" && isTransactionPrompt(request.payload) ? request.payload : undefined;
423
499
  useEffect(() => {
424
- if (!services2 || !trustState || !p1satIntent || !intent)
500
+ if (!services || !txPrompt?.trust || !txPrompt.verify)
425
501
  return;
426
502
  let live = true;
427
- verifyIntent(services2, p1satIntent, intent.inputs, intent.outputs, services2.ordfs?.getContentUrl).then((res) => {
503
+ const getContentUrl = services.ordfs?.getContentUrl?.bind(services.ordfs);
504
+ verifyIntent(services, txPrompt.verify.kind, txPrompt.verify.inputs, txPrompt.verify.outputs, getContentUrl).then((res) => {
428
505
  if (live && res)
429
506
  setVerified(res);
430
507
  }).catch(() => {
@@ -433,8 +510,8 @@ function OneSatPermissionPrompt({
433
510
  return () => {
434
511
  live = false;
435
512
  };
436
- }, [services2, trustState, p1satIntent, intent]);
437
- const summary = verified ? applyVerification(base, verified) : base;
513
+ }, [services, txPrompt]);
514
+ const view = verified ? applyVerification(base, verified) : base;
438
515
  const handle = (action) => () => {
439
516
  if (busy)
440
517
  return;
@@ -461,7 +538,6 @@ function OneSatPermissionPrompt({
461
538
  return () => window.removeEventListener("keydown", onKey);
462
539
  }, [onApprove, onReject, busy]);
463
540
  const className = `opp-root${resolvedTheme === "dark" ? " opp-dark" : ""}`;
464
- const featuredVariant = summary.featured?.variant ?? "ordinal";
465
541
  return /* @__PURE__ */ jsxs("div", {
466
542
  className,
467
543
  children: [
@@ -506,11 +582,11 @@ function OneSatPermissionPrompt({
506
582
  }),
507
583
  /* @__PURE__ */ jsx("h2", {
508
584
  className: "opp-title",
509
- children: summary.title
585
+ children: view.title
510
586
  }),
511
587
  /* @__PURE__ */ jsx("p", {
512
588
  className: "opp-subtitle",
513
- children: summary.subtitle
589
+ children: view.subtitle
514
590
  }),
515
591
  /* @__PURE__ */ jsx("div", {
516
592
  className: "opp-status",
@@ -518,58 +594,79 @@ function OneSatPermissionPrompt({
518
594
  })
519
595
  ]
520
596
  }),
521
- summary.featured && /* @__PURE__ */ jsxs("div", {
522
- className: `opp-featured${featuredVariant === "token" ? " opp-featured-token" : ""}`,
523
- children: [
524
- summary.featured.imageUrl ? /* @__PURE__ */ jsx("img", {
525
- className: featuredVariant === "token" ? "opp-featured-image opp-featured-image-token" : "opp-featured-image",
526
- src: summary.featured.imageUrl,
527
- alt: summary.featured.title
528
- }) : /* @__PURE__ */ jsx("div", {
529
- className: featuredVariant === "token" ? "opp-featured-image opp-featured-image-token opp-featured-placeholder" : "opp-featured-image opp-featured-placeholder",
530
- "aria-hidden": true,
531
- children: featuredVariant === "token" ? "◎" : "▭"
532
- }),
533
- /* @__PURE__ */ jsxs("div", {
534
- className: "opp-featured-meta",
535
- children: [
536
- /* @__PURE__ */ jsx("div", {
537
- className: "opp-featured-title",
538
- children: summary.featured.title
539
- }),
540
- summary.featured.subtitle && /* @__PURE__ */ jsxs("div", {
541
- className: "opp-featured-subtitle",
542
- children: [
543
- /* @__PURE__ */ jsx("span", {
544
- className: "opp-featured-subtitle-text",
545
- children: summary.featured.subtitle
546
- }),
547
- summary.featured.subtitleCopy && /* @__PURE__ */ jsx(CopyButton, {
548
- text: summary.featured.subtitleCopy
549
- })
550
- ]
551
- })
552
- ]
553
- })
554
- ]
597
+ view.panels.map((panel, i) => {
598
+ const variant = panel.variant ?? "ordinal";
599
+ const classes = ["opp-featured"];
600
+ if (variant === "token")
601
+ classes.push("opp-featured-token");
602
+ if (variant === "value")
603
+ classes.push("opp-featured-value");
604
+ if (panel.tone === "danger")
605
+ classes.push("opp-featured-danger");
606
+ return /* @__PURE__ */ jsxs("div", {
607
+ className: classes.join(" "),
608
+ children: [
609
+ /* @__PURE__ */ jsx(PanelPreview, {
610
+ panel
611
+ }),
612
+ /* @__PURE__ */ jsxs("div", {
613
+ className: "opp-featured-meta",
614
+ children: [
615
+ /* @__PURE__ */ jsx("div", {
616
+ className: "opp-featured-title",
617
+ children: panel.title
618
+ }),
619
+ panel.subtitle && /* @__PURE__ */ jsxs("div", {
620
+ className: "opp-featured-subtitle",
621
+ children: [
622
+ /* @__PURE__ */ jsx("span", {
623
+ className: panel.amountSats != null ? "opp-featured-subtitle-text opp-amount" : "opp-featured-subtitle-text",
624
+ children: panel.subtitle
625
+ }),
626
+ panel.subtitleCopy && /* @__PURE__ */ jsx(CopyButton, {
627
+ text: panel.subtitleCopy
628
+ })
629
+ ]
630
+ }),
631
+ (panel.meta ?? []).map((m) => /* @__PURE__ */ jsxs("div", {
632
+ className: `opp-featured-meta-line${m.key ? "" : " opp-featured-meta-line-bare"}`,
633
+ children: [
634
+ m.key ? /* @__PURE__ */ jsx("span", {
635
+ className: "opp-featured-meta-key",
636
+ children: m.key
637
+ }) : null,
638
+ /* @__PURE__ */ jsx("span", {
639
+ className: `opp-featured-meta-value${m.key ? "" : " opp-featured-meta-value-mono"}`,
640
+ title: m.copyValue ?? m.value,
641
+ children: m.value
642
+ }),
643
+ m.copyValue && /* @__PURE__ */ jsx(CopyButton, {
644
+ text: m.copyValue
645
+ })
646
+ ]
647
+ }, `${m.key}:${m.value}`))
648
+ ]
649
+ })
650
+ ]
651
+ }, `panel-${i}-${panel.title}`);
555
652
  }),
556
- summary.trust && /* @__PURE__ */ jsxs("div", {
653
+ view.trust && /* @__PURE__ */ jsxs("div", {
557
654
  className: "opp-trust-wrap",
558
655
  children: [
559
656
  /* @__PURE__ */ jsx("span", {
560
- className: `opp-trust opp-trust-${summary.trust.state}`,
561
- children: trustLabel(summary.trust.state)
657
+ className: `opp-trust opp-trust-${view.trust.state}`,
658
+ children: trustLabel(view.trust.state)
562
659
  }),
563
- summary.trust.note && /* @__PURE__ */ jsx("div", {
564
- className: `opp-trust-note opp-trust-note-${summary.trust.state}`,
565
- children: summary.trust.note
660
+ view.trust.note && /* @__PURE__ */ jsx("div", {
661
+ className: `opp-trust-note opp-trust-note-${view.trust.state}`,
662
+ children: view.trust.note
566
663
  })
567
664
  ]
568
665
  }),
569
- (summary.rows.length > 0 || summary.indexerFee) && /* @__PURE__ */ jsxs("div", {
666
+ (view.rows.length > 0 || view.indexerFee || view.funding) && /* @__PURE__ */ jsxs("div", {
570
667
  className: "opp-card",
571
668
  children: [
572
- summary.rows.map((row) => /* @__PURE__ */ jsxs("div", {
669
+ view.rows.map((row) => /* @__PURE__ */ jsxs("div", {
573
670
  className: "opp-row",
574
671
  children: [
575
672
  /* @__PURE__ */ jsx("span", {
@@ -591,29 +688,41 @@ function OneSatPermissionPrompt({
591
688
  })
592
689
  ]
593
690
  }, `${row.key}:${row.value}`)),
594
- summary.indexerFee && /* @__PURE__ */ jsxs(Fragment, {
691
+ view.indexerFee && /* @__PURE__ */ jsxs("div", {
692
+ className: "opp-row",
595
693
  children: [
596
- /* @__PURE__ */ jsxs("div", {
597
- className: "opp-row",
694
+ /* @__PURE__ */ jsx("span", {
695
+ className: "opp-row-key",
696
+ children: "1Sat Overlay Fee"
697
+ }),
698
+ /* @__PURE__ */ jsxs("span", {
699
+ className: "opp-row-value opp-amount",
598
700
  children: [
599
- /* @__PURE__ */ jsx("span", {
600
- className: "opp-row-key",
601
- children: "Indexer fee"
602
- }),
603
- /* @__PURE__ */ jsxs("span", {
604
- className: "opp-row-value",
605
- children: [
606
- formatSats(summary.indexerFee.sats),
607
- " sats"
608
- ]
609
- })
701
+ formatSats(view.indexerFee.sats),
702
+ " sats"
610
703
  ]
704
+ })
705
+ ]
706
+ }),
707
+ view.funding?.networkFeeSats != null && /* @__PURE__ */ jsxs("div", {
708
+ className: "opp-row",
709
+ children: [
710
+ /* @__PURE__ */ jsx("span", {
711
+ className: "opp-row-key",
712
+ children: "Network fee"
611
713
  }),
612
- summary.indexerFee.note && /* @__PURE__ */ jsx("div", {
613
- className: "opp-fee-note",
614
- children: summary.indexerFee.note
714
+ /* @__PURE__ */ jsxs("span", {
715
+ className: "opp-row-value opp-amount",
716
+ children: [
717
+ formatSats(view.funding.networkFeeSats),
718
+ " sats"
719
+ ]
615
720
  })
616
721
  ]
722
+ }),
723
+ view.funding?.note && /* @__PURE__ */ jsx("div", {
724
+ className: "opp-fee-note",
725
+ children: view.funding.note
617
726
  })
618
727
  ]
619
728
  }),
@@ -650,6 +759,120 @@ function OneSatPermissionPrompt({
650
759
  ]
651
760
  });
652
761
  }
762
+ function viewFromRequest(req) {
763
+ if (req.kind === "transaction" && isTransactionPrompt(req.payload)) {
764
+ return {
765
+ title: req.payload.title,
766
+ subtitle: req.payload.subtitle,
767
+ panels: req.payload.panels,
768
+ rows: req.payload.rows,
769
+ trust: req.payload.trust,
770
+ indexerFee: req.payload.indexerFee,
771
+ funding: req.payload.funding
772
+ };
773
+ }
774
+ if (req.kind === "basketAccess") {
775
+ const baskets = req.payload.baskets ?? [];
776
+ const isView = baskets.length > 0 && baskets.every((b) => b.scope || b.basket.startsWith("p "));
777
+ const panels = baskets.filter((b) => b.imageUrl || b.name || b.value).map((b) => ({
778
+ title: b.scope === "collection" ? b.name ? `Collection · ${b.name}` : "Collection" : b.scope ? b.scope : "Basket",
779
+ subtitle: b.value ? b.value.length > 24 ? `${b.value.slice(0, 10)}…${b.value.slice(-10)}` : b.value : b.basket,
780
+ subtitleCopy: b.value,
781
+ ...b.imageUrl && {
782
+ previewKind: "image",
783
+ contentUrl: b.imageUrl,
784
+ imageUrl: b.imageUrl
785
+ },
786
+ tone: "default"
787
+ }));
788
+ const rows = baskets.flatMap((b) => {
789
+ if (b.scope === "collection") {
790
+ return b.description ? [{ key: "Access", value: b.description }] : [];
791
+ }
792
+ return [
793
+ {
794
+ key: b.basket,
795
+ value: b.description ?? "List, insert, and remove outputs"
796
+ }
797
+ ];
798
+ });
799
+ return {
800
+ title: isView ? baskets.length === 1 ? baskets[0].scope === "collection" ? "Grant Collection View" : "Grant Item View" : `Grant View (${baskets.length})` : baskets.length === 1 ? "Grant Basket Access" : `Grant Access to ${baskets.length} Baskets`,
801
+ subtitle: isView ? `${shortenOriginator(req.originator)} wants to view items in your wallet` : `${shortenOriginator(req.originator)} wants to read and write 1Sat baskets`,
802
+ panels,
803
+ rows
804
+ };
805
+ }
806
+ if (req.kind === "protocol") {
807
+ return {
808
+ title: "Protocol Access",
809
+ subtitle: `${shortenOriginator(req.originator)} wants protocol access`,
810
+ panels: [],
811
+ rows: [{ key: "Detail", value: req.summary || "Protocol request" }]
812
+ };
813
+ }
814
+ return {
815
+ title: "Signature Request",
816
+ subtitle: `${shortenOriginator(req.originator)} wants a signature`,
817
+ panels: [],
818
+ rows: [{ key: "Detail", value: req.summary || "Signature request" }]
819
+ };
820
+ }
821
+ function applyVerification(base, res) {
822
+ const panels = base.panels.map((p, i) => {
823
+ if (i !== 0)
824
+ return p;
825
+ const keepOpns = p.previewKind === "opns";
826
+ const kindFromVerify = res.contentType ? previewKindFromContentType(res.contentType) : undefined;
827
+ const nextKind = keepOpns ? "opns" : kindFromVerify && kindFromVerify !== "none" ? kindFromVerify : p.previewKind;
828
+ const mediaUrl = res.contentUrl ?? p.contentUrl ?? p.imageUrl;
829
+ let subtitle = p.subtitle;
830
+ if (res.name && !keepOpns) {
831
+ if (!subtitle) {
832
+ subtitle = res.name;
833
+ } else {
834
+ const parts = subtitle.trim().split(/\s+/);
835
+ if (parts.length >= 2) {
836
+ parts[parts.length - 1] = res.name;
837
+ subtitle = parts.join(" ");
838
+ }
839
+ }
840
+ }
841
+ return {
842
+ ...p,
843
+ previewKind: nextKind,
844
+ ...subtitle ? { subtitle } : {},
845
+ ...keepOpns ? {
846
+ ...res.contentUrl && !p.imageUrl ? { imageUrl: res.contentUrl } : p.imageUrl ? { imageUrl: p.imageUrl } : {}
847
+ } : nextKind === "image" ? { imageUrl: mediaUrl, contentUrl: mediaUrl } : mediaUrl ? { contentUrl: mediaUrl, imageUrl: undefined } : {}
848
+ };
849
+ });
850
+ return {
851
+ ...base,
852
+ panels,
853
+ trust: { state: res.state, note: res.note }
854
+ };
855
+ }
856
+ function previewKindFromContentType(ct) {
857
+ const c = ct.toLowerCase();
858
+ if (c === "image/svg+xml" || c === "svg" || c.endsWith("+svg"))
859
+ return "svg";
860
+ if (c.startsWith("image/"))
861
+ return "image";
862
+ if (c.includes("json"))
863
+ return "json";
864
+ if (c.includes("html") || c === "application/xhtml+xml")
865
+ return "html";
866
+ if (c.startsWith("text/"))
867
+ return "text";
868
+ return "none";
869
+ }
870
+ function formatSatsFull(n) {
871
+ return `${n.toLocaleString("en-US")} sats`;
872
+ }
873
+ function formatSats(n) {
874
+ return n.toLocaleString("en-US");
875
+ }
653
876
  function trustLabel(state) {
654
877
  if (state === "verified")
655
878
  return "Verified";
@@ -657,8 +880,218 @@ function trustLabel(state) {
657
880
  return "Mismatch";
658
881
  return "Unverified";
659
882
  }
660
- function formatSats(n) {
661
- return n.toLocaleString("en-US");
883
+ function PanelPreview({ panel }) {
884
+ const variant = panel.variant ?? "ordinal";
885
+ const baseClass = variant === "token" ? "opp-featured-image opp-featured-image-token" : variant === "value" ? "opp-featured-image opp-preview-value" : "opp-featured-image";
886
+ const kind = panel.previewKind ?? (panel.imageUrl ? "image" : "none");
887
+ const mediaUrl = panel.contentUrl ?? panel.imageUrl;
888
+ const [imgBroken, setImgBroken] = useState(false);
889
+ if (variant === "value") {
890
+ const icon = panel.valueIcon ?? "pay";
891
+ return /* @__PURE__ */ jsx("div", {
892
+ className: baseClass,
893
+ title: panel.amountSats != null ? formatSatsFull(panel.amountSats) : undefined,
894
+ "aria-hidden": true,
895
+ children: /* @__PURE__ */ jsx(ValueGlyph, {
896
+ kind: icon
897
+ })
898
+ });
899
+ }
900
+ if (kind === "opns") {
901
+ const showAvatar = Boolean(panel.imageUrl) && !imgBroken;
902
+ return /* @__PURE__ */ jsxs("div", {
903
+ className: `${baseClass} opp-preview-opns`,
904
+ "aria-hidden": !panel.opnsHero,
905
+ children: [
906
+ showAvatar ? /* @__PURE__ */ jsx("img", {
907
+ className: "opp-preview-opns-avatar",
908
+ src: panel.imageUrl,
909
+ alt: "",
910
+ onError: () => setImgBroken(true)
911
+ }) : /* @__PURE__ */ jsx("div", {
912
+ className: "opp-preview-opns-avatar opp-featured-placeholder",
913
+ children: "◈"
914
+ }),
915
+ /* @__PURE__ */ jsx("span", {
916
+ className: "opp-preview-opns-name",
917
+ title: panel.opnsHero,
918
+ children: panel.opnsHero ?? "OpNS"
919
+ })
920
+ ]
921
+ });
922
+ }
923
+ if (kind === "image" && panel.imageUrl && !imgBroken) {
924
+ return /* @__PURE__ */ jsx("img", {
925
+ className: baseClass,
926
+ src: panel.imageUrl,
927
+ alt: panel.title,
928
+ onError: () => setImgBroken(true)
929
+ });
930
+ }
931
+ if (kind === "image" && imgBroken) {
932
+ return /* @__PURE__ */ jsx("div", {
933
+ className: `${baseClass} opp-featured-placeholder`,
934
+ "aria-hidden": true,
935
+ children: "▭"
936
+ });
937
+ }
938
+ if ((kind === "text" || kind === "json" || kind === "html") && panel.previewText) {
939
+ return /* @__PURE__ */ jsx("pre", {
940
+ className: `${baseClass} opp-preview-text${kind === "json" ? " opp-preview-json" : ""}`,
941
+ children: panel.previewText
942
+ });
943
+ }
944
+ if ((kind === "text" || kind === "json") && mediaUrl) {
945
+ return /* @__PURE__ */ jsx(FetchedTextPreview, {
946
+ url: mediaUrl,
947
+ kind,
948
+ className: baseClass
949
+ });
950
+ }
951
+ if ((kind === "html" || kind === "svg") && mediaUrl) {
952
+ return /* @__PURE__ */ jsx("iframe", {
953
+ className: `${baseClass} opp-preview-frame`,
954
+ src: mediaUrl,
955
+ title: `${panel.title} preview`,
956
+ sandbox: "",
957
+ referrerPolicy: "no-referrer"
958
+ });
959
+ }
960
+ return /* @__PURE__ */ jsx("div", {
961
+ className: `${baseClass} opp-featured-placeholder`,
962
+ "aria-hidden": true,
963
+ children: variant === "token" ? "◎" : previewGlyph(kind)
964
+ });
965
+ }
966
+ function previewGlyph(kind) {
967
+ if (kind === "text" || kind === "json")
968
+ return "¶";
969
+ if (kind === "html" || kind === "svg")
970
+ return "⌂";
971
+ return "▭";
972
+ }
973
+ function ValueGlyph({ kind }) {
974
+ if (kind === "lock") {
975
+ return /* @__PURE__ */ jsxs("svg", {
976
+ className: "opp-preview-value-icon",
977
+ viewBox: "0 0 24 24",
978
+ fill: "none",
979
+ "aria-hidden": true,
980
+ children: [
981
+ /* @__PURE__ */ jsx("rect", {
982
+ x: "5",
983
+ y: "11",
984
+ width: "14",
985
+ height: "10",
986
+ rx: "2",
987
+ stroke: "currentColor",
988
+ strokeWidth: "2"
989
+ }),
990
+ /* @__PURE__ */ jsx("path", {
991
+ d: "M8 11V8a4 4 0 018 0v3",
992
+ stroke: "currentColor",
993
+ strokeWidth: "2",
994
+ strokeLinecap: "round"
995
+ })
996
+ ]
997
+ });
998
+ }
999
+ if (kind === "unlock") {
1000
+ return /* @__PURE__ */ jsxs("svg", {
1001
+ className: "opp-preview-value-icon",
1002
+ viewBox: "0 0 24 24",
1003
+ fill: "none",
1004
+ "aria-hidden": true,
1005
+ children: [
1006
+ /* @__PURE__ */ jsx("rect", {
1007
+ x: "5",
1008
+ y: "11",
1009
+ width: "14",
1010
+ height: "10",
1011
+ rx: "2",
1012
+ stroke: "currentColor",
1013
+ strokeWidth: "2"
1014
+ }),
1015
+ /* @__PURE__ */ jsx("path", {
1016
+ d: "M8 11V7.5A4 4 0 0116 7",
1017
+ stroke: "currentColor",
1018
+ strokeWidth: "2",
1019
+ strokeLinecap: "round"
1020
+ }),
1021
+ /* @__PURE__ */ jsx("path", {
1022
+ d: "M16 4.5v3",
1023
+ stroke: "currentColor",
1024
+ strokeWidth: "2",
1025
+ strokeLinecap: "round"
1026
+ })
1027
+ ]
1028
+ });
1029
+ }
1030
+ return /* @__PURE__ */ jsxs("svg", {
1031
+ className: "opp-preview-value-icon",
1032
+ viewBox: "0 0 24 24",
1033
+ fill: "none",
1034
+ "aria-hidden": true,
1035
+ children: [
1036
+ /* @__PURE__ */ jsx("circle", {
1037
+ cx: "12",
1038
+ cy: "12",
1039
+ r: "9",
1040
+ stroke: "currentColor",
1041
+ strokeWidth: "2"
1042
+ }),
1043
+ /* @__PURE__ */ jsx("path", {
1044
+ d: "M12 7v10M9 10.5c0-1.4 1.3-2.5 3-2.5s3 1.1 3 2.5-1.3 2.5-3 2.5-3 1.1-3 2.5 1.3 2.5 3 2.5 3-1.1 3-2.5",
1045
+ stroke: "currentColor",
1046
+ strokeWidth: "2",
1047
+ strokeLinecap: "round"
1048
+ })
1049
+ ]
1050
+ });
1051
+ }
1052
+ function FetchedTextPreview({
1053
+ url,
1054
+ kind,
1055
+ className
1056
+ }) {
1057
+ const [body, setBody] = useState(null);
1058
+ const [failed, setFailed] = useState(false);
1059
+ useEffect(() => {
1060
+ let cancelled = false;
1061
+ setBody(null);
1062
+ setFailed(false);
1063
+ fetch(url).then(async (res) => {
1064
+ if (!res.ok)
1065
+ throw new Error(String(res.status));
1066
+ const text = await res.text();
1067
+ if (cancelled)
1068
+ return;
1069
+ if (kind === "json") {
1070
+ try {
1071
+ setBody(JSON.stringify(JSON.parse(text), null, 2));
1072
+ return;
1073
+ } catch {}
1074
+ }
1075
+ setBody(text.slice(0, 800));
1076
+ }).catch(() => {
1077
+ if (!cancelled)
1078
+ setFailed(true);
1079
+ });
1080
+ return () => {
1081
+ cancelled = true;
1082
+ };
1083
+ }, [url, kind]);
1084
+ if (failed) {
1085
+ return /* @__PURE__ */ jsx("div", {
1086
+ className: `${className} opp-featured-placeholder`,
1087
+ "aria-hidden": true,
1088
+ children: "¶"
1089
+ });
1090
+ }
1091
+ return /* @__PURE__ */ jsx("pre", {
1092
+ className: `${className} opp-preview-text${kind === "json" ? " opp-preview-json" : ""}`,
1093
+ children: body ?? "…"
1094
+ });
662
1095
  }
663
1096
  function CopyButton({ text }) {
664
1097
  const [copied, setCopied] = useState(false);
@@ -713,9 +1146,6 @@ function CopyButton({ text }) {
713
1146
  })
714
1147
  });
715
1148
  }
716
- function copyable(key, full) {
717
- return { key, value: shortenValue(full), copyValue: full };
718
- }
719
1149
  function useResolvedTheme(theme) {
720
1150
  const [resolved, setResolved] = useState(() => theme === "auto" ? readSystemTheme() : theme);
721
1151
  useEffect(() => {
@@ -736,596 +1166,15 @@ function readSystemTheme() {
736
1166
  return "light";
737
1167
  return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
738
1168
  }
739
- function applyVerification(base, res) {
740
- const rows = base.rows.map((row) => {
741
- if (row.key === "Origin" && res.origin)
742
- return copyable("Origin", res.origin);
743
- if (row.key === "Type" && res.contentType) {
744
- return { key: "Type", value: res.contentType };
745
- }
746
- return row;
747
- });
748
- if (res.contentLength !== undefined && !rows.some((r) => r.key === "Size")) {
749
- rows.push({
750
- key: "Size",
751
- value: `${res.contentLength.toLocaleString("en-US")} bytes`
752
- });
753
- }
754
- return {
755
- ...base,
756
- rows,
757
- trust: { state: res.state, note: res.note },
758
- featured: base.featured ? {
759
- ...base.featured,
760
- title: res.name ?? base.featured.title,
761
- imageUrl: res.contentUrl ?? base.featured.imageUrl
762
- } : undefined
763
- };
764
- }
765
- function summarizeRequest(req) {
766
- if (req.kind === "transaction") {
767
- const intent = req.intent;
768
- const byIntent = summarizeByP1SatIntent(req, intent);
769
- if (byIntent)
770
- return byIntent;
771
- switch (intent.kind) {
772
- case "ordinal-transfer":
773
- return summarizeOrdinalTransfer(req, intent);
774
- case "token-transfer":
775
- return summarizeTokenTransfer(req, intent);
776
- case "lock":
777
- return summarizeLock(req, intent);
778
- case "unlock":
779
- return summarizeUnlock(req, intent);
780
- case "inscription":
781
- return summarizeInscription(req, intent);
782
- case "listing":
783
- return summarizeListing(req, intent);
784
- case "cancel-listing":
785
- return summarizeCancelListing(req, intent);
786
- case "purchase":
787
- return summarizePurchase(req, intent);
788
- case "social-post":
789
- return summarizeSocialPost(req, intent);
790
- case "opns":
791
- return summarizeOpns(req, intent);
792
- default:
793
- return summarizeUnknownTx(req, intent);
794
- }
795
- }
796
- if (req.kind === "protocol") {
797
- return summarizeProtocol(req);
798
- }
799
- if (req.kind === "basketAccess") {
800
- return summarizeBasketAccess(req);
801
- }
802
- return summarizeSignature(req);
803
- }
804
- function summarizeBasketAccess(req) {
805
- const intent = req.intent;
806
- const baskets = intent.baskets ?? [];
807
- return {
808
- title: baskets.length === 1 ? "Grant Basket Access" : `Grant Access to ${baskets.length} Baskets`,
809
- subtitle: `${shortenOriginator(req.originator)} wants to read and write 1Sat baskets`,
810
- rows: baskets.map((b) => ({
811
- key: b.basket,
812
- value: b.description ?? "List, insert, and remove outputs"
813
- }))
814
- };
815
- }
816
- var INTENT_TITLES = {
817
- "opns.register": "Publish name",
818
- "opns.deregister": "Unpublish name",
819
- "opns.list": "List name for sale",
820
- "opns.transfer": "Transfer name",
821
- "opns.cancel-listing": "Cancel listing",
822
- "opns.purchase": "Buy name",
823
- "ordinal.transfer": "Send ordinal",
824
- "ordinal.list": "List for sale",
825
- "ordinal.cancel-listing": "Cancel listing",
826
- "ordinal.purchase": "Buy ordinal",
827
- "ordinal.burn": "Burn ordinal",
828
- "ordinal.inscribe": "Create inscription",
829
- "ordinal.inscribe-sigma": "Create inscription",
830
- "ordinal.mint-collection": "Mint collection",
831
- "ordinal.mint-item": "Mint collection item",
832
- "lock.lock": "Lock BSV",
833
- "lock.unlock": "Unlock BSV",
834
- "bsv21.transfer": "Send tokens",
835
- "bsv21.purchase": "Buy tokens",
836
- "bsv21.mint": "Mint tokens",
837
- "bsv21.deploy": "Deploy token"
838
- };
839
- function summarizeByP1SatIntent(req, intent) {
840
- const id = intent.p1satIntent;
841
- if (!id || !INTENT_TITLES[id])
842
- return;
843
- let base;
844
- switch (intent.kind) {
845
- case "ordinal-transfer":
846
- base = summarizeOrdinalTransfer(req, intent);
847
- break;
848
- case "token-transfer":
849
- base = summarizeTokenTransfer(req, intent);
850
- break;
851
- case "lock":
852
- base = summarizeLock(req, intent);
853
- break;
854
- case "unlock":
855
- base = summarizeUnlock(req, intent);
856
- break;
857
- case "inscription":
858
- base = summarizeInscription(req, intent);
859
- break;
860
- case "listing":
861
- base = summarizeListing(req, intent);
862
- break;
863
- case "cancel-listing":
864
- base = summarizeCancelListing(req, intent);
865
- break;
866
- case "purchase":
867
- base = summarizePurchase(req, intent);
868
- break;
869
- case "opns":
870
- base = summarizeOpns(req, intent);
871
- break;
872
- default:
873
- base = summarizeUnknownTx(req, intent);
874
- }
875
- return {
876
- ...base,
877
- title: INTENT_TITLES[id],
878
- subtitle: intentSubtitle(req, id, intent),
879
- feeSats: undefined,
880
- network: undefined,
881
- trust: intent.trust ?? base.trust,
882
- indexerFee: intent.indexerFeeSats != null && intent.indexerFeeSats > 0 ? {
883
- sats: intent.indexerFeeSats,
884
- note: intent.indexerFeeNote
885
- } : base.indexerFee
886
- };
887
- }
888
- function intentSubtitle(req, id, intent) {
889
- const o = shortenOriginator(req.originator);
890
- const name = tagValue(intent.inputs[0]?.tags, "name") ?? tagValue(intent.outputs.find((out) => out.tags.some((t) => t.startsWith("name:")))?.tags, "name");
891
- const quoted = name ? `“${name}”` : undefined;
892
- switch (id) {
893
- case "opns.register":
894
- return `${o} wants to publish ${quoted ?? "an OpNS name"}`;
895
- case "opns.deregister":
896
- return `${o} wants to unpublish ${quoted ?? "an OpNS name"}`;
897
- case "opns.list":
898
- return `${o} wants to list ${quoted ?? "an OpNS name"} for sale`;
899
- case "opns.transfer":
900
- return `${o} wants to transfer ${quoted ?? "an OpNS name"}`;
901
- case "opns.cancel-listing":
902
- return `${o} wants to cancel ${quoted ? `the listing of ${quoted}` : "an OpNS listing"}`;
903
- case "opns.purchase":
904
- return `${o} wants to buy ${quoted ?? "an OpNS name"}`;
905
- case "ordinal.transfer":
906
- return `${o} wants to transfer an ordinal`;
907
- case "ordinal.list":
908
- return `${o} wants to list ${quoted ?? "an ordinal"} for sale`;
909
- case "ordinal.cancel-listing":
910
- return `${o} wants to cancel ${quoted ? `the listing of ${quoted}` : "an ordinal listing"}`;
911
- case "ordinal.purchase":
912
- return `${o} wants to purchase an ordinal into your wallet`;
913
- case "ordinal.burn":
914
- return `${o} wants to burn an ordinal`;
915
- case "ordinal.inscribe":
916
- case "ordinal.inscribe-sigma":
917
- return `${o} wants to inscribe content into your wallet`;
918
- case "ordinal.mint-collection":
919
- return `${o} wants to mint a collection`;
920
- case "ordinal.mint-item":
921
- return `${o} wants to mint a collection item`;
922
- case "lock.lock":
923
- return `${o} wants to time-lock funds`;
924
- case "lock.unlock":
925
- return `${o} wants to unlock matured locks`;
926
- case "bsv21.transfer":
927
- return `${o} wants to send tokens`;
928
- case "bsv21.purchase":
929
- return `${o} wants to purchase tokens into your wallet`;
930
- case "bsv21.mint":
931
- return `${o} wants to mint tokens`;
932
- case "bsv21.deploy":
933
- return `${o} wants to deploy a BSV21 token`;
934
- default:
935
- return `${o} wants to ${req.summary.toLowerCase()}`;
936
- }
937
- }
938
- function tagValue(tags, key) {
939
- if (!tags)
940
- return;
941
- const prefix = `${key}:`;
942
- for (const t of tags) {
943
- if (t.startsWith(prefix))
944
- return t.slice(prefix.length);
945
- }
946
- return;
947
- }
948
- function summarizeOrdinalTransfer(req, intent) {
949
- const ordinal = intent.inputs[0];
950
- const origin = tagValue(ordinal?.tags, "origin");
951
- const name = tagValue(ordinal?.tags, "name");
952
- const contentType = tagValue(ordinal?.tags, "type");
953
- const collectionId = tagValue(ordinal?.tags, "collectionId");
954
- const recipient = intent.outputs.find((o) => o.recipient)?.recipient;
955
- const imageUrl = origin ? intent.contentUrls?.[origin] : undefined;
956
- const rows = [];
957
- if (recipient)
958
- rows.push(copyable("Recipient", recipient));
959
- if (origin)
960
- rows.push(copyable("Origin", origin));
961
- if (contentType)
962
- rows.push({ key: "Type", value: contentType });
963
- return {
964
- title: "Send ordinal",
965
- subtitle: `${shortenOriginator(req.originator)} wants to transfer an ordinal`,
966
- rows,
967
- featured: imageUrl ? {
968
- imageUrl,
969
- title: name ?? "Untitled ordinal",
970
- subtitle: collectionId ?? contentType
971
- } : undefined
972
- };
973
- }
974
- function summarizeTokenTransfer(req, intent) {
975
- const totals = new Map;
976
- for (const input of intent.inputs) {
977
- const tokenId = tagValue(input.tags, "bsv21");
978
- if (!tokenId)
979
- continue;
980
- const sym2 = tagValue(input.tags, "sym");
981
- const amt = BigInt(tagValue(input.tags, "amt") ?? "0");
982
- const cur = totals.get(tokenId) ?? { sym: sym2, inAmt: 0n, changeAmt: 0n };
983
- cur.inAmt += amt;
984
- if (!cur.sym && sym2)
985
- cur.sym = sym2;
986
- totals.set(tokenId, cur);
987
- }
988
- for (const output of intent.outputs) {
989
- if (output.basket !== BSV21_BASKET)
990
- continue;
991
- const tokenId = tagValue(output.tags, "bsv21");
992
- if (!tokenId)
993
- continue;
994
- const amt = BigInt(tagValue(output.tags, "amt") ?? "0");
995
- const cur = totals.get(tokenId);
996
- if (cur)
997
- cur.changeAmt += amt;
998
- }
999
- const rows = [];
1000
- let featured;
1001
- let primaryTokenId;
1002
- for (const [tokenId, info] of totals) {
1003
- if (!primaryTokenId)
1004
- primaryTokenId = tokenId;
1005
- const sent = info.inAmt - info.changeAmt;
1006
- rows.push({ key: "Amount", value: sent.toLocaleString("en-US") });
1007
- }
1008
- const to = intent.outputs.find((o) => o.recipient && o.satoshis === 1)?.recipient;
1009
- if (to)
1010
- rows.push(copyable("Recipient", to));
1011
- const firstIn = intent.inputs.find((i) => tagValue(i.tags, "bsv21"));
1012
- const sym = (primaryTokenId ? totals.get(primaryTokenId)?.sym : undefined) ?? tagValue(firstIn?.tags, "sym");
1013
- const iconRef = tagValue(firstIn?.tags, "icon");
1014
- const iconUrl = iconRef && intent.contentUrls?.[iconRef] || primaryTokenId && intent.contentUrls?.[primaryTokenId] || undefined;
1015
- if (primaryTokenId) {
1016
- featured = {
1017
- variant: "token",
1018
- imageUrl: iconUrl,
1019
- title: sym ?? "Token",
1020
- subtitle: shortenId(primaryTokenId),
1021
- subtitleCopy: primaryTokenId
1022
- };
1023
- }
1024
- const indexerFee = resolveIndexerFee(intent);
1025
- return {
1026
- title: "Send tokens",
1027
- subtitle: `${shortenOriginator(req.originator)} wants to send tokens`,
1028
- rows,
1029
- featured,
1030
- indexerFee
1031
- };
1032
- }
1033
- function resolveIndexerFee(intent) {
1034
- if (intent.indexerFeeSats != null && intent.indexerFeeSats > 0) {
1035
- return {
1036
- sats: intent.indexerFeeSats,
1037
- note: intent.indexerFeeNote
1038
- };
1039
- }
1040
- const tokenOutCount = intent.outputs.filter((o) => o.basket === BSV21_BASKET || tagValue(o.tags, "bsv21")).length;
1041
- const feeLike = intent.outputs.filter((o) => !o.basket && o.recipient && o.satoshis > 1);
1042
- const isPurchase = intent.p1satIntent?.endsWith(".purchase") || intent.kind === "purchase";
1043
- if (isPurchase || feeLike.length !== 1)
1044
- return;
1045
- const fee = feeLike[0];
1046
- if (!fee || fee.satoshis <= 0)
1047
- return;
1048
- const note = tokenOutCount > 0 ? `Overlay processing · ${formatSats(Math.round(fee.satoshis / tokenOutCount))} sats × ${tokenOutCount} token outs` : "Overlay processing fee";
1049
- return { sats: fee.satoshis, note };
1050
- }
1051
- function summarizeLock(req, intent) {
1052
- const decoded = intent.outputs.filter((o) => o.lockUntilHeight !== undefined);
1053
- const lockOutputs = decoded.length > 0 ? decoded : intent.outputs.filter((o) => o.basket === LOCK_BASKET);
1054
- const sats = (lockOutputs.length > 0 ? lockOutputs : intent.outputs).reduce((s, o) => s + o.satoshis, 0);
1055
- const until = decoded[0]?.lockUntilHeight;
1056
- const rows = [
1057
- { key: "Amount", value: `${sats.toLocaleString("en-US")} sats` }
1058
- ];
1059
- if (until !== undefined) {
1060
- rows.push({
1061
- key: "Lock until block",
1062
- value: until.toLocaleString("en-US")
1063
- });
1064
- }
1065
- return {
1066
- title: "Lock BSV",
1067
- subtitle: `${shortenOriginator(req.originator)} wants to time-lock funds`,
1068
- rows
1069
- };
1070
- }
1071
- function summarizeUnlock(req, intent) {
1072
- const sats = intent.inputs.reduce((s, i) => s + i.satoshis, 0);
1073
- return {
1074
- title: "Unlock Matured",
1075
- subtitle: `${shortenOriginator(req.originator)} wants to unlock matured locks`,
1076
- rows: [{ key: "Amount", value: `${sats} sats` }]
1077
- };
1078
- }
1079
- function summarizeInscription(req, intent) {
1080
- const inscriptionOutput = intent.outputs.find((o) => o.tags.some((t) => t.startsWith("type:"))) ?? intent.outputs[0];
1081
- const contentType = tagValue(inscriptionOutput?.tags, "type");
1082
- const name = tagValue(inscriptionOutput?.tags, "name");
1083
- const recipient = inscriptionOutput?.recipient;
1084
- const rows = [];
1085
- if (contentType)
1086
- rows.push({ key: "Type", value: contentType });
1087
- if (name)
1088
- rows.push({ key: "Name", value: name });
1089
- if (recipient)
1090
- rows.push(copyable("Recipient", recipient));
1091
- return {
1092
- title: "Create Inscription",
1093
- subtitle: `${shortenOriginator(req.originator)} wants to inscribe content into your wallet`,
1094
- rows
1095
- };
1096
- }
1097
- function summarizeListing(req, intent) {
1098
- const listingOutput = intent.outputs.find((o) => o.listingPriceSats !== undefined) ?? intent.outputs.find((o) => o.tags.some((t) => t.startsWith("price:")));
1099
- const ordinal = intent.inputs[0];
1100
- const origin = tagValue(ordinal?.tags, "origin") ?? tagValue(listingOutput?.tags, "origin");
1101
- const name = tagValue(ordinal?.tags, "name") ?? tagValue(listingOutput?.tags, "name");
1102
- const contentType = tagValue(ordinal?.tags, "type") ?? tagValue(listingOutput?.tags, "type");
1103
- const collectionId = tagValue(ordinal?.tags, "collectionId");
1104
- const imageUrl = origin ? intent.contentUrls?.[origin] : undefined;
1105
- const price = listingOutput?.listingPriceSats;
1106
- const rows = [];
1107
- if (name)
1108
- rows.push({ key: "Name", value: name });
1109
- if (price !== undefined) {
1110
- rows.push({ key: "Price", value: `${price.toLocaleString("en-US")} sats` });
1111
- }
1112
- if (origin)
1113
- rows.push(copyable("Origin", origin));
1114
- if (contentType)
1115
- rows.push({ key: "Type", value: contentType });
1116
- return {
1117
- title: "List Ordinal for Sale",
1118
- subtitle: `${shortenOriginator(req.originator)} wants to list ${name ? `“${name}”` : "an ordinal"} for sale`,
1119
- rows,
1120
- featured: imageUrl ? {
1121
- imageUrl,
1122
- title: name ?? "Untitled ordinal",
1123
- subtitle: collectionId ?? contentType
1124
- } : undefined
1125
- };
1126
- }
1127
- function summarizeCancelListing(req, intent) {
1128
- const listing = intent.inputs[0];
1129
- const reclaimed = intent.outputs[0];
1130
- const origin = tagValue(listing?.tags, "origin") ?? tagValue(reclaimed?.tags, "origin");
1131
- const name = tagValue(listing?.tags, "name") ?? tagValue(reclaimed?.tags, "name");
1132
- const contentType = tagValue(listing?.tags, "type") ?? tagValue(reclaimed?.tags, "type");
1133
- const collectionId = tagValue(listing?.tags, "collectionId");
1134
- const price = tagValue(listing?.tags, "price");
1135
- const imageUrl = origin ? intent.contentUrls?.[origin] : undefined;
1136
- const rows = [];
1137
- if (name)
1138
- rows.push({ key: "Name", value: name });
1139
- if (price)
1140
- rows.push({ key: "Original price", value: `${price} sats` });
1141
- if (origin)
1142
- rows.push(copyable("Origin", origin));
1143
- if (contentType)
1144
- rows.push({ key: "Type", value: contentType });
1145
- return {
1146
- title: "Cancel Listing",
1147
- subtitle: `${shortenOriginator(req.originator)} wants to cancel ${name ? `the listing of “${name}”` : "an ordinal listing"}`,
1148
- rows,
1149
- featured: imageUrl ? {
1150
- imageUrl,
1151
- title: name ?? "Untitled ordinal",
1152
- subtitle: collectionId ?? contentType
1153
- } : undefined
1154
- };
1155
- }
1156
- function summarizePurchase(req, intent) {
1157
- const incoming = intent.outputs.find((o) => o.basket === ORDINALS_BASKET || o.basket === BSV21_BASKET);
1158
- const isToken = incoming?.basket === BSV21_BASKET;
1159
- const origin = tagValue(incoming?.tags, "origin");
1160
- const name = tagValue(incoming?.tags, "name");
1161
- const contentType = tagValue(incoming?.tags, "type");
1162
- const collectionId = tagValue(incoming?.tags, "collectionId");
1163
- const sym = tagValue(incoming?.tags, "sym");
1164
- const amt = tagValue(incoming?.tags, "amt");
1165
- const tokenId = tagValue(incoming?.tags, "bsv21");
1166
- const sellerOutput = intent.outputs.find((o) => !o.basket && o.recipient && o.satoshis > 1);
1167
- const price = sellerOutput?.satoshis;
1168
- const rows = [];
1169
- if (price !== undefined)
1170
- rows.push({ key: "Price", value: `${price.toLocaleString("en-US")} sats` });
1171
- if (isToken) {
1172
- if (amt)
1173
- rows.push({ key: "Amount", value: amt });
1174
- } else {
1175
- if (origin)
1176
- rows.push(copyable("Origin", origin));
1177
- if (contentType)
1178
- rows.push({ key: "Type", value: contentType });
1179
- }
1180
- const featuredImage = !isToken && origin ? intent.contentUrls?.[origin] : undefined;
1181
- const iconRef = tagValue(incoming?.tags, "icon");
1182
- const iconUrl = iconRef && intent.contentUrls?.[iconRef] || tokenId && intent.contentUrls?.[tokenId] || undefined;
1183
- let featured;
1184
- if (isToken && (sym || tokenId)) {
1185
- featured = {
1186
- variant: "token",
1187
- imageUrl: iconUrl,
1188
- title: sym ?? "Token",
1189
- subtitle: tokenId ? shortenId(tokenId) : undefined,
1190
- subtitleCopy: tokenId
1191
- };
1192
- } else if (featuredImage) {
1193
- featured = {
1194
- imageUrl: featuredImage,
1195
- title: name ?? "Untitled ordinal",
1196
- subtitle: collectionId ?? contentType
1197
- };
1198
- }
1199
- return {
1200
- title: isToken ? "Buy tokens" : "Buy ordinal",
1201
- subtitle: isToken ? `${shortenOriginator(req.originator)} wants to purchase tokens into your wallet` : `${shortenOriginator(req.originator)} wants to purchase an ordinal into your wallet`,
1202
- rows,
1203
- featured,
1204
- trust: intent.trust,
1205
- indexerFee: resolveIndexerFee(intent)
1206
- };
1207
- }
1208
- function summarizeSocialPost(req, intent) {
1209
- return {
1210
- title: "Social Post",
1211
- subtitle: `${shortenOriginator(req.originator)} wants to publish a post`,
1212
- rows: []
1213
- };
1214
- }
1215
- function summarizeOpns(req, intent) {
1216
- const input = intent.inputs[0];
1217
- const opnsOutput = intent.outputs.find((o) => o.basket === OPNS_BASKET);
1218
- const recipient = intent.outputs.find((o) => !o.basket && o.recipient)?.recipient;
1219
- const name = tagValue(input?.tags, "name") ?? tagValue(opnsOutput?.tags, "name");
1220
- const origin = tagValue(input?.tags, "origin") ?? tagValue(opnsOutput?.tags, "origin");
1221
- const priceTag = intent.outputs.flatMap((o) => o.tags).find((t) => t.startsWith("price:"));
1222
- const price = priceTag?.slice("price:".length);
1223
- const rows = [];
1224
- if (name)
1225
- rows.push({ key: "Name", value: name });
1226
- if (opnsOutput?.opnsProfileName) {
1227
- rows.push({ key: "Display name", value: opnsOutput.opnsProfileName });
1228
- }
1229
- if (recipient)
1230
- rows.push(copyable("Recipient", recipient));
1231
- if (price)
1232
- rows.push({ key: "Price", value: `${price} sats` });
1233
- if (origin)
1234
- rows.push(copyable("Origin", origin));
1235
- const copy = opnsCopy(intent.p1satIntent, Boolean(recipient));
1236
- const nameBit = name ? `“${name}”` : "an OpNS name";
1237
- const avatarOrigin = opnsOutput?.opnsAvatarOrigin;
1238
- const avatarUrl = avatarOrigin ? intent.contentUrls?.[avatarOrigin] : undefined;
1239
- return {
1240
- title: copy.title,
1241
- subtitle: `${shortenOriginator(req.originator)} wants to ${copy.verb} ${nameBit}`,
1242
- rows,
1243
- ...avatarUrl && {
1244
- featured: {
1245
- variant: "token",
1246
- imageUrl: avatarUrl,
1247
- title: opnsOutput?.opnsProfileName ?? name ?? "Avatar",
1248
- ...name && { subtitle: name }
1249
- }
1250
- }
1251
- };
1252
- }
1253
- function opnsCopy(p1satIntent, isTransferHeuristic) {
1254
- switch (p1satIntent) {
1255
- case "opns.register":
1256
- return { title: "Publish name", verb: "publish" };
1257
- case "opns.deregister":
1258
- return { title: "Unpublish name", verb: "unpublish" };
1259
- case "opns.list":
1260
- return { title: "List name for sale", verb: "list" };
1261
- case "opns.transfer":
1262
- return { title: "Transfer name", verb: "transfer" };
1263
- case "opns.cancel-listing":
1264
- return { title: "Cancel listing", verb: "cancel the listing of" };
1265
- case "opns.purchase":
1266
- return { title: "Buy name", verb: "buy" };
1267
- default:
1268
- return isTransferHeuristic ? { title: "Transfer OpNS Name", verb: "transfer" } : { title: "Update OpNS Name", verb: "update" };
1269
- }
1270
- }
1271
- function summarizeUnknownTx(req, intent) {
1272
- const ins = intent.inputs.length;
1273
- const outs = intent.outputs.length;
1274
- return {
1275
- title: "Transaction Request",
1276
- subtitle: `${shortenOriginator(req.originator)} wants to ${req.summary.toLowerCase()}`,
1277
- rows: [{ key: "Inputs / Outputs", value: `${ins} / ${outs}` }]
1278
- };
1279
- }
1280
- function shortenId(id, max = 24) {
1281
- if (id.length <= max)
1282
- return id;
1283
- return `${id.slice(0, 8)}…${id.slice(-6)}`;
1284
- }
1285
- function shortenValue(value, head = 12, tail = 10) {
1286
- if (value.length <= head + tail + 1)
1287
- return value;
1288
- return `${value.slice(0, head)}…${value.slice(-tail)}`;
1289
- }
1290
- function summarizeProtocol(req) {
1291
- const intent = req.intent;
1292
- const rows = [
1293
- { key: "Protocol", value: "1Sat (p 1sat)" },
1294
- { key: "Access", value: intent.access ?? "read-only" }
1295
- ];
1296
- return {
1297
- title: "Protocol Access",
1298
- subtitle: intent.notes ?? `${shortenOriginator(req.originator)} requests read-only access to derive your 1Sat addresses. Signing remains per-transaction.`,
1299
- rows
1300
- };
1301
- }
1302
- function summarizeSignature(req) {
1303
- const intent = req.intent;
1304
- const rows = [];
1305
- if (intent.protocolID) {
1306
- rows.push({ key: "Protocol", value: JSON.stringify(intent.protocolID) });
1307
- }
1308
- if (intent.keyID)
1309
- rows.push({ key: "Key ID", value: intent.keyID });
1310
- if (intent.counterparty) {
1311
- rows.push({
1312
- key: "Counterparty",
1313
- value: shortenOriginator(intent.counterparty)
1314
- });
1315
- }
1316
- if (typeof intent.dataLength === "number") {
1317
- rows.push({ key: "Payload", value: `${intent.dataLength} bytes` });
1318
- }
1319
- return {
1320
- title: "Signature Request",
1321
- subtitle: `${shortenOriginator(req.originator)} requests a signature`,
1322
- rows
1323
- };
1324
- }
1325
1169
  function shortenOriginator(origin) {
1326
- if (origin.length <= 28)
1327
- return origin;
1328
- return `${origin.slice(0, 18)}…${origin.slice(-6)}`;
1170
+ try {
1171
+ const u = new URL(origin.includes("://") ? origin : `https://${origin}`);
1172
+ return u.host || origin;
1173
+ } catch {
1174
+ if (origin.length <= 32)
1175
+ return origin;
1176
+ return `${origin.slice(0, 14)}…${origin.slice(-12)}`;
1177
+ }
1329
1178
  }
1330
1179
  export {
1331
1180
  OneSatPermissionPrompt