feat(bns): name marketplace and TLD-owner co-sign rule
Two gaps the owner panel left open. First, a hidden TLD was only a UI gate: anyone could still broadcast a REG under it and every indexer admitted it. Second, there was no way to sell a name without trusting the other side. Co-sign rule (consensus, applied in lockstep by bns.js and resolver-web.js): a REG under a TLD whose records at that height say policy "cosign" or hidden 1 is indexed only if the transaction carries the TLD's own certificate. The certificate can only be spent by the owner's key and is re-issued to them in the same transaction, so it is a co-signature nobody can forge and nothing is consumed. The TLD map now keeps the TUPD timeline so policy is evaluated at the REG height. Owners register under their private TLDs with the certificate added from their own wallet; third parties under a "cosign" TLD build the full transaction, sign their inputs and queue it at /api/cosign, where the owner approves it from the dashboard (signCosignRequest refuses to sign unless the certificate returns to the same locking script). Marketplace: a listing is the seller's certificate input plus a price output signed SIGHASH_SINGLE|ANYONECANPAY, stored by the gateway as a bulletin board (/api/market, verified against the on-chain owner and pruned when the certificate moves). The buyer completes it in one transaction, so the seller is paid exactly when the name moves. Cancelling also spends the certificate once so the offer is void. Site: market.html, Sell sub-tab and Pending approvals in the portal, Market link in nav and footer, six dictionaries extended, cache tags bumped. Verified on chipnet: cosigned.sc registered by a throwaway wallet through the queue with the .sc certificate back at the owner; aloevera.test listed and delisted through the API. Ariadne's resolver-web.js copy and the mobile Bns.java port still need the co-sign rule; until then they admit REGs this index rejects.
This commit is contained in:
parent
5099069907
commit
9351045f45
18 changed files with 788 additions and 29 deletions
|
|
@ -92,7 +92,7 @@
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||||
</script>
|
</script>
|
||||||
<script src="../js/i18n.js?v=20260917g"></script>
|
<script src="../js/i18n.js?v=20260917h"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -101,6 +101,7 @@
|
||||||
<a href="../#try">Try it</a>
|
<a href="../#try">Try it</a>
|
||||||
<a href="../#search-input">✏️ Register</a>
|
<a href="../#search-input">✏️ Register</a>
|
||||||
<a href="../tld.html">🌐 TLD</a>
|
<a href="../tld.html">🌐 TLD</a>
|
||||||
|
<a href="../market.html">🛒 Market</a>
|
||||||
<a href="../theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
|
<a href="../theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
|
||||||
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
<a href="../portal.html" class="portal">🔑 Sign in</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -525,8 +526,8 @@ process.exit(0);"</pre>
|
||||||
|
|
||||||
<script src="../js/session.js?v=20260917dash"></script>
|
<script src="../js/session.js?v=20260917dash"></script>
|
||||||
<script src="../js/pin-escrow.js?v=20260909pin"></script>
|
<script src="../js/pin-escrow.js?v=20260909pin"></script>
|
||||||
<script src="../js/pricing.js?v=20260909tldhigh"></script>
|
<script src="../js/pricing.js?v=20260917market"></script>
|
||||||
<script defer src="../js/site-footer.js?v=20260907rel"></script>
|
<script defer src="../js/site-footer.js?v=20260917market"></script>
|
||||||
<script defer src="../js/theme.js?v=20260910font"></script>
|
<script defer src="../js/theme.js?v=20260910font"></script>
|
||||||
<script defer src="../js/profile-menu.js?v=20260917dash"></script>
|
<script defer src="../js/profile-menu.js?v=20260917dash"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
41
i18n/de.json
41
i18n/de.json
|
|
@ -544,5 +544,44 @@
|
||||||
"Wallet settings": "Wallet-Einstellungen",
|
"Wallet settings": "Wallet-Einstellungen",
|
||||||
"session, PIN, forget device": "Sitzung, PIN, Gerät vergessen",
|
"session, PIN, forget device": "Sitzung, PIN, Gerät vergessen",
|
||||||
"Holdings": "Bestände",
|
"Holdings": "Bestände",
|
||||||
"open the dashboard to load": "Dashboard öffnen zum Laden"
|
"open the dashboard to load": "Dashboard öffnen zum Laden",
|
||||||
|
"🛒 Market": "🛒 Markt",
|
||||||
|
"🛒 Names for sale": "🛒 Namen zum Verkauf",
|
||||||
|
"Market — names for sale on Sirius.X": "Markt — Namen zum Verkauf auf Sirius.X",
|
||||||
|
"for sale": "zum Verkauf",
|
||||||
|
"Every sale is a single Bitcoin Cash transaction. The seller signed an offer that pays them only when the certificate moves to you — you add your coins, sign, broadcast. No escrow, no middleman, no way for either side to cheat.": "Jeder Verkauf ist eine einzige Bitcoin-Cash-Transaktion. Der Verkäufer hat ein Angebot signiert, das ihn nur bezahlt, wenn das Zertifikat zu dir wechselt — du fügst deine Coins hinzu, signierst, sendest. Kein Treuhänder, kein Mittelsmann, keine Möglichkeit zu betrügen.",
|
||||||
|
"Sort: price": "Sortieren: Preis",
|
||||||
|
"Loading listings…": "Angebote werden geladen…",
|
||||||
|
"Selling a name?": "Einen Namen verkaufen?",
|
||||||
|
"Open it in your": "Öffne ihn in deinem",
|
||||||
|
", choose": ", wähle",
|
||||||
|
"Sell": "Verkaufen",
|
||||||
|
", set a price. The listing is a signed offer stored by the gateway; cancelling it moves your certificate once so the old offer can never be used.": ", lege einen Preis fest. Das Angebot ist eine signierte Offerte, die das Gateway speichert; beim Zurückziehen wird dein Zertifikat einmal bewegt, damit die alte Offerte nie mehr verwendet werden kann.",
|
||||||
|
"One transaction: your coins pay the seller, the certificate lands in your wallet, and the registry is told about the new owner. Records stay as they are — you can change them in the dashboard right after.": "Eine Transaktion: deine Coins bezahlen den Verkäufer, das Zertifikat landet in deiner Wallet, und das Register erfährt vom neuen Besitzer. Die Einträge bleiben wie sie sind — du kannst sie direkt danach im Dashboard ändern.",
|
||||||
|
"Sign in to buy →": "Zum Kauf anmelden →",
|
||||||
|
"Buy — sign & broadcast": "Kaufen — signieren & senden",
|
||||||
|
"Buy →": "Kaufen →",
|
||||||
|
"Nothing is for sale right now. Owners list names from their dashboard.": "Derzeit steht nichts zum Verkauf. Besitzer bieten Namen über ihr Dashboard an.",
|
||||||
|
"Sell this name": "Diesen Namen verkaufen",
|
||||||
|
"You sign an offer once: \"whoever pays me this price gets the certificate\". The offer is stored by the gateway and shown on the": "Du signierst einmal ein Angebot: „wer mir diesen Preis zahlt, bekommt das Zertifikat“. Das Angebot speichert das Gateway und zeigt es auf dem",
|
||||||
|
"market": "Markt",
|
||||||
|
". A buyer completes it in one transaction — you are paid in the same transaction that moves the name, or not at all. No escrow, nothing to trust.": ". Ein Käufer schließt es in einer Transaktion ab — du wirst in derselben Transaktion bezahlt, die den Namen überträgt, oder gar nicht. Kein Treuhänder, nichts, dem man vertrauen muss.",
|
||||||
|
"Checking the market…": "Markt wird geprüft…",
|
||||||
|
"Price (USD)": "Preis (USD)",
|
||||||
|
"Price (sat)": "Preis (Sat)",
|
||||||
|
"List for sale →": "Zum Verkauf anbieten →",
|
||||||
|
"Cancel listing →": "Angebot zurückziehen →",
|
||||||
|
"Cancelling removes the offer from the market and moves your certificate once (a no-op update, a few cents), so the signed offer can never be completed later.": "Beim Zurückziehen wird das Angebot vom Markt entfernt und dein Zertifikat einmal bewegt (ein leeres Update, ein paar Cent), damit die signierte Offerte später nie mehr abgeschlossen werden kann.",
|
||||||
|
"Not for sale. Set a price to list it.": "Nicht zum Verkauf. Lege einen Preis fest, um ihn anzubieten.",
|
||||||
|
"This name is listed on the market.": "Dieser Name ist auf dem Markt gelistet.",
|
||||||
|
"Pending approvals": "Ausstehende Freigaben",
|
||||||
|
"Registrations under this TLD that wait for your co-signature. Approving signs your TLD certificate into the buyer's transaction and broadcasts it; the certificate returns to you in the same transaction. Nothing can be registered under a co-sign or private TLD without this step.": "Registrierungen unter dieser TLD, die auf deine Gegensignatur warten. Beim Freigeben wird dein TLD-Zertifikat in die Transaktion des Käufers signiert und gesendet; das Zertifikat kommt in derselben Transaktion zu dir zurück. Ohne diesen Schritt kann unter einer Co-Sign- oder privaten TLD nichts registriert werden.",
|
||||||
|
"No requests waiting.": "Keine Anfragen offen.",
|
||||||
|
"awaiting you": "wartet auf dich",
|
||||||
|
"Approve & broadcast": "Freigeben & senden",
|
||||||
|
"Decline": "Ablehnen",
|
||||||
|
"Request approval — sign": "Freigabe anfragen — signieren",
|
||||||
|
"Withdraw request": "Anfrage zurückziehen",
|
||||||
|
"Request declined": "Anfrage abgelehnt",
|
||||||
|
"Open dashboard →": "Dashboard öffnen →"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
43
i18n/el.json
43
i18n/el.json
|
|
@ -459,7 +459,7 @@
|
||||||
"No names yet.": "Δεν υπάρχουν ακόμα ονόματα.",
|
"No names yet.": "Δεν υπάρχουν ακόμα ονόματα.",
|
||||||
"Register your first one →": "Καταχώρησε το πρώτο σου →",
|
"Register your first one →": "Καταχώρησε το πρώτο σου →",
|
||||||
"Everything points somewhere and has DNS. Nice.": "Όλα δείχνουν κάπου και έχουν DNS. Ωραία.",
|
"Everything points somewhere and has DNS. Nice.": "Όλα δείχνουν κάπου και έχουν DNS. Ωραία.",
|
||||||
"Nothing matches that filter.": "Τίποτα δεν ταιριάζει σε αυτό το φίλτρο.",
|
"Nothing matches that filter.": "Τίποτα δεν ταιριάζει με αυτό το φίλτρο.",
|
||||||
"Status": "Κατάσταση",
|
"Status": "Κατάσταση",
|
||||||
"point it somewhere →": "δείξε κάπου →",
|
"point it somewhere →": "δείξε κάπου →",
|
||||||
"Add some →": "Πρόσθεσε →",
|
"Add some →": "Πρόσθεσε →",
|
||||||
|
|
@ -544,5 +544,44 @@
|
||||||
"Wallet settings": "Ρυθμίσεις πορτοφολιού",
|
"Wallet settings": "Ρυθμίσεις πορτοφολιού",
|
||||||
"session, PIN, forget device": "συνεδρία, PIN, λήθη συσκευής",
|
"session, PIN, forget device": "συνεδρία, PIN, λήθη συσκευής",
|
||||||
"Holdings": "Κατοχές",
|
"Holdings": "Κατοχές",
|
||||||
"open the dashboard to load": "άνοιξε τον πίνακα για φόρτωση"
|
"open the dashboard to load": "άνοιξε τον πίνακα για φόρτωση",
|
||||||
|
"🛒 Market": "🛒 Αγορά",
|
||||||
|
"🛒 Names for sale": "🛒 Ονόματα προς πώληση",
|
||||||
|
"Market — names for sale on Sirius.X": "Αγορά — ονόματα προς πώληση στο Sirius.X",
|
||||||
|
"for sale": "προς πώληση",
|
||||||
|
"Every sale is a single Bitcoin Cash transaction. The seller signed an offer that pays them only when the certificate moves to you — you add your coins, sign, broadcast. No escrow, no middleman, no way for either side to cheat.": "Κάθε πώληση είναι μία μόνο συναλλαγή Bitcoin Cash. Ο πωλητής υπέγραψε μια προσφορά που τον πληρώνει μόνο όταν το πιστοποιητικό περάσει σε εσένα — προσθέτεις τα κέρματά σου, υπογράφεις, μεταδίδεις. Χωρίς μεσεγγύηση, χωρίς μεσάζοντα, χωρίς τρόπο να εξαπατήσει κανείς.",
|
||||||
|
"Sort: price": "Ταξινόμηση: τιμή",
|
||||||
|
"Loading listings…": "Φόρτωση αγγελιών…",
|
||||||
|
"Selling a name?": "Πουλάς ένα όνομα;",
|
||||||
|
"Open it in your": "Άνοιξέ το στον",
|
||||||
|
", choose": ", επίλεξε",
|
||||||
|
"Sell": "Πώληση",
|
||||||
|
", set a price. The listing is a signed offer stored by the gateway; cancelling it moves your certificate once so the old offer can never be used.": ", όρισε τιμή. Η αγγελία είναι μια υπογεγραμμένη προσφορά που αποθηκεύει η πύλη· η ακύρωση μετακινεί το πιστοποιητικό σου μία φορά ώστε η παλιά προσφορά να μην μπορεί ποτέ να χρησιμοποιηθεί.",
|
||||||
|
"One transaction: your coins pay the seller, the certificate lands in your wallet, and the registry is told about the new owner. Records stay as they are — you can change them in the dashboard right after.": "Μία συναλλαγή: τα κέρματά σου πληρώνουν τον πωλητή, το πιστοποιητικό φτάνει στο πορτοφόλι σου και το μητρώο ενημερώνεται για τον νέο ιδιοκτήτη. Οι εγγραφές μένουν ως έχουν — μπορείς να τις αλλάξεις αμέσως μετά στον πίνακα ελέγχου.",
|
||||||
|
"Sign in to buy →": "Σύνδεση για αγορά →",
|
||||||
|
"Buy — sign & broadcast": "Αγορά — υπογραφή & μετάδοση",
|
||||||
|
"Buy →": "Αγορά →",
|
||||||
|
"Nothing is for sale right now. Owners list names from their dashboard.": "Τίποτα δεν πωλείται αυτή τη στιγμή. Οι ιδιοκτήτες καταχωρούν ονόματα από τον πίνακα ελέγχου τους.",
|
||||||
|
"Sell this name": "Πώληση αυτού του ονόματος",
|
||||||
|
"You sign an offer once: \"whoever pays me this price gets the certificate\". The offer is stored by the gateway and shown on the": "Υπογράφεις μία προσφορά μία φορά: «όποιος μου πληρώσει αυτή την τιμή παίρνει το πιστοποιητικό». Η προσφορά αποθηκεύεται από την πύλη και εμφανίζεται στην",
|
||||||
|
"market": "αγορά",
|
||||||
|
". A buyer completes it in one transaction — you are paid in the same transaction that moves the name, or not at all. No escrow, nothing to trust.": ". Ο αγοραστής την ολοκληρώνει σε μία συναλλαγή — πληρώνεσαι στην ίδια συναλλαγή που μεταφέρει το όνομα, ή καθόλου. Χωρίς μεσεγγύηση, τίποτα προς εμπιστοσύνη.",
|
||||||
|
"Checking the market…": "Έλεγχος της αγοράς…",
|
||||||
|
"Price (USD)": "Τιμή (USD)",
|
||||||
|
"Price (sat)": "Τιμή (sat)",
|
||||||
|
"List for sale →": "Καταχώρηση προς πώληση →",
|
||||||
|
"Cancel listing →": "Ακύρωση αγγελίας →",
|
||||||
|
"Cancelling removes the offer from the market and moves your certificate once (a no-op update, a few cents), so the signed offer can never be completed later.": "Η ακύρωση αφαιρεί την προσφορά από την αγορά και μετακινεί το πιστοποιητικό σου μία φορά (μια κενή ενημέρωση, λίγα λεπτά), ώστε η υπογεγραμμένη προσφορά να μην μπορεί ποτέ να ολοκληρωθεί αργότερα.",
|
||||||
|
"Not for sale. Set a price to list it.": "Δεν πωλείται. Όρισε τιμή για να το καταχωρήσεις.",
|
||||||
|
"This name is listed on the market.": "Αυτό το όνομα είναι καταχωρημένο στην αγορά.",
|
||||||
|
"Pending approvals": "Εκκρεμείς εγκρίσεις",
|
||||||
|
"Registrations under this TLD that wait for your co-signature. Approving signs your TLD certificate into the buyer's transaction and broadcasts it; the certificate returns to you in the same transaction. Nothing can be registered under a co-sign or private TLD without this step.": "Καταχωρήσεις κάτω από αυτό το TLD που περιμένουν τη συνυπογραφή σου. Η έγκριση υπογράφει το πιστοποιητικό TLD σου μέσα στη συναλλαγή του αγοραστή και τη μεταδίδει· το πιστοποιητικό επιστρέφει σε εσένα στην ίδια συναλλαγή. Τίποτα δεν μπορεί να καταχωρηθεί κάτω από ένα TLD συνυπογραφής ή ιδιωτικό χωρίς αυτό το βήμα.",
|
||||||
|
"No requests waiting.": "Δεν εκκρεμούν αιτήματα.",
|
||||||
|
"awaiting you": "περιμένει εσένα",
|
||||||
|
"Approve & broadcast": "Έγκριση & μετάδοση",
|
||||||
|
"Decline": "Απόρριψη",
|
||||||
|
"Request approval — sign": "Αίτημα έγκρισης — υπογραφή",
|
||||||
|
"Withdraw request": "Απόσυρση αιτήματος",
|
||||||
|
"Request declined": "Το αίτημα απορρίφθηκε",
|
||||||
|
"Open dashboard →": "Άνοιγμα πίνακα ελέγχου →"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
i18n/es.json
41
i18n/es.json
|
|
@ -544,5 +544,44 @@
|
||||||
"Wallet settings": "Ajustes de la cartera",
|
"Wallet settings": "Ajustes de la cartera",
|
||||||
"session, PIN, forget device": "sesión, PIN, olvidar dispositivo",
|
"session, PIN, forget device": "sesión, PIN, olvidar dispositivo",
|
||||||
"Holdings": "Propiedades",
|
"Holdings": "Propiedades",
|
||||||
"open the dashboard to load": "abre el panel para cargar"
|
"open the dashboard to load": "abre el panel para cargar",
|
||||||
|
"🛒 Market": "🛒 Mercado",
|
||||||
|
"🛒 Names for sale": "🛒 Nombres en venta",
|
||||||
|
"Market — names for sale on Sirius.X": "Mercado — nombres en venta en Sirius.X",
|
||||||
|
"for sale": "en venta",
|
||||||
|
"Every sale is a single Bitcoin Cash transaction. The seller signed an offer that pays them only when the certificate moves to you — you add your coins, sign, broadcast. No escrow, no middleman, no way for either side to cheat.": "Cada venta es una única transacción de Bitcoin Cash. El vendedor firmó una oferta que solo le paga cuando el certificado pasa a ti — añades tus monedas, firmas, transmites. Sin custodia, sin intermediarios, sin forma de que nadie haga trampa.",
|
||||||
|
"Sort: price": "Ordenar: precio",
|
||||||
|
"Loading listings…": "Cargando anuncios…",
|
||||||
|
"Selling a name?": "¿Vendes un nombre?",
|
||||||
|
"Open it in your": "Ábrelo en tu",
|
||||||
|
", choose": ", elige",
|
||||||
|
"Sell": "Vender",
|
||||||
|
", set a price. The listing is a signed offer stored by the gateway; cancelling it moves your certificate once so the old offer can never be used.": ", fija un precio. El anuncio es una oferta firmada que guarda la pasarela; cancelarlo mueve tu certificado una vez para que la oferta antigua nunca pueda usarse.",
|
||||||
|
"One transaction: your coins pay the seller, the certificate lands in your wallet, and the registry is told about the new owner. Records stay as they are — you can change them in the dashboard right after.": "Una transacción: tus monedas pagan al vendedor, el certificado llega a tu billetera y el registro conoce al nuevo dueño. Los registros quedan como están — puedes cambiarlos en el panel justo después.",
|
||||||
|
"Sign in to buy →": "Inicia sesión para comprar →",
|
||||||
|
"Buy — sign & broadcast": "Comprar — firmar y transmitir",
|
||||||
|
"Buy →": "Comprar →",
|
||||||
|
"Nothing is for sale right now. Owners list names from their dashboard.": "Ahora mismo no hay nada en venta. Los dueños publican nombres desde su panel.",
|
||||||
|
"Sell this name": "Vender este nombre",
|
||||||
|
"You sign an offer once: \"whoever pays me this price gets the certificate\". The offer is stored by the gateway and shown on the": "Firmas una oferta una vez: «quien me pague este precio se queda con el certificado». La pasarela guarda la oferta y la muestra en el",
|
||||||
|
"market": "mercado",
|
||||||
|
". A buyer completes it in one transaction — you are paid in the same transaction that moves the name, or not at all. No escrow, nothing to trust.": ". Un comprador la completa en una transacción — cobras en la misma transacción que mueve el nombre, o no cobras. Sin custodia, nada en lo que confiar.",
|
||||||
|
"Checking the market…": "Consultando el mercado…",
|
||||||
|
"Price (USD)": "Precio (USD)",
|
||||||
|
"Price (sat)": "Precio (sat)",
|
||||||
|
"List for sale →": "Poner en venta →",
|
||||||
|
"Cancel listing →": "Cancelar anuncio →",
|
||||||
|
"Cancelling removes the offer from the market and moves your certificate once (a no-op update, a few cents), so the signed offer can never be completed later.": "Cancelar retira la oferta del mercado y mueve tu certificado una vez (una actualización vacía, unos céntimos), para que la oferta firmada nunca pueda completarse después.",
|
||||||
|
"Not for sale. Set a price to list it.": "No está en venta. Fija un precio para publicarlo.",
|
||||||
|
"This name is listed on the market.": "Este nombre está publicado en el mercado.",
|
||||||
|
"Pending approvals": "Aprobaciones pendientes",
|
||||||
|
"Registrations under this TLD that wait for your co-signature. Approving signs your TLD certificate into the buyer's transaction and broadcasts it; the certificate returns to you in the same transaction. Nothing can be registered under a co-sign or private TLD without this step.": "Registros bajo este TLD que esperan tu cofirma. Aprobar firma tu certificado de TLD dentro de la transacción del comprador y la transmite; el certificado vuelve a ti en la misma transacción. Nada puede registrarse bajo un TLD de cofirma o privado sin este paso.",
|
||||||
|
"No requests waiting.": "No hay solicitudes en espera.",
|
||||||
|
"awaiting you": "te espera",
|
||||||
|
"Approve & broadcast": "Aprobar y transmitir",
|
||||||
|
"Decline": "Rechazar",
|
||||||
|
"Request approval — sign": "Solicitar aprobación — firmar",
|
||||||
|
"Withdraw request": "Retirar solicitud",
|
||||||
|
"Request declined": "Solicitud rechazada",
|
||||||
|
"Open dashboard →": "Abrir panel →"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
i18n/fr.json
41
i18n/fr.json
|
|
@ -544,5 +544,44 @@
|
||||||
"Wallet settings": "Réglages du portefeuille",
|
"Wallet settings": "Réglages du portefeuille",
|
||||||
"session, PIN, forget device": "session, PIN, oublier l'appareil",
|
"session, PIN, forget device": "session, PIN, oublier l'appareil",
|
||||||
"Holdings": "Avoirs",
|
"Holdings": "Avoirs",
|
||||||
"open the dashboard to load": "ouvrez le tableau de bord pour charger"
|
"open the dashboard to load": "ouvrez le tableau de bord pour charger",
|
||||||
|
"🛒 Market": "🛒 Marché",
|
||||||
|
"🛒 Names for sale": "🛒 Noms à vendre",
|
||||||
|
"Market — names for sale on Sirius.X": "Marché — noms à vendre sur Sirius.X",
|
||||||
|
"for sale": "à vendre",
|
||||||
|
"Every sale is a single Bitcoin Cash transaction. The seller signed an offer that pays them only when the certificate moves to you — you add your coins, sign, broadcast. No escrow, no middleman, no way for either side to cheat.": "Chaque vente est une seule transaction Bitcoin Cash. Le vendeur a signé une offre qui ne le paie que lorsque le certificat vous est transféré — vous ajoutez vos pièces, signez, diffusez. Pas de séquestre, pas d'intermédiaire, aucun moyen de tricher.",
|
||||||
|
"Sort: price": "Trier : prix",
|
||||||
|
"Loading listings…": "Chargement des annonces…",
|
||||||
|
"Selling a name?": "Vous vendez un nom ?",
|
||||||
|
"Open it in your": "Ouvrez-le dans votre",
|
||||||
|
", choose": ", choisissez",
|
||||||
|
"Sell": "Vendre",
|
||||||
|
", set a price. The listing is a signed offer stored by the gateway; cancelling it moves your certificate once so the old offer can never be used.": ", fixez un prix. L'annonce est une offre signée conservée par la passerelle ; l'annuler déplace votre certificat une fois afin que l'ancienne offre ne puisse plus jamais servir.",
|
||||||
|
"One transaction: your coins pay the seller, the certificate lands in your wallet, and the registry is told about the new owner. Records stay as they are — you can change them in the dashboard right after.": "Une transaction : vos pièces paient le vendeur, le certificat arrive dans votre portefeuille et le registre apprend le nouveau propriétaire. Les enregistrements restent tels quels — vous pouvez les modifier dans le tableau de bord juste après.",
|
||||||
|
"Sign in to buy →": "Se connecter pour acheter →",
|
||||||
|
"Buy — sign & broadcast": "Acheter — signer et diffuser",
|
||||||
|
"Buy →": "Acheter →",
|
||||||
|
"Nothing is for sale right now. Owners list names from their dashboard.": "Rien n'est à vendre pour l'instant. Les propriétaires mettent leurs noms en vente depuis leur tableau de bord.",
|
||||||
|
"Sell this name": "Vendre ce nom",
|
||||||
|
"You sign an offer once: \"whoever pays me this price gets the certificate\". The offer is stored by the gateway and shown on the": "Vous signez une offre une seule fois : « quiconque me paie ce prix obtient le certificat ». La passerelle conserve l'offre et l'affiche sur le",
|
||||||
|
"market": "marché",
|
||||||
|
". A buyer completes it in one transaction — you are paid in the same transaction that moves the name, or not at all. No escrow, nothing to trust.": ". Un acheteur la complète en une transaction — vous êtes payé dans la même transaction qui transfère le nom, ou pas du tout. Pas de séquestre, rien à devoir croire.",
|
||||||
|
"Checking the market…": "Vérification du marché…",
|
||||||
|
"Price (USD)": "Prix (USD)",
|
||||||
|
"Price (sat)": "Prix (sat)",
|
||||||
|
"List for sale →": "Mettre en vente →",
|
||||||
|
"Cancel listing →": "Annuler l'annonce →",
|
||||||
|
"Cancelling removes the offer from the market and moves your certificate once (a no-op update, a few cents), so the signed offer can never be completed later.": "L'annulation retire l'offre du marché et déplace votre certificat une fois (une mise à jour vide, quelques centimes), afin que l'offre signée ne puisse jamais être complétée plus tard.",
|
||||||
|
"Not for sale. Set a price to list it.": "Pas à vendre. Fixez un prix pour le mettre en vente.",
|
||||||
|
"This name is listed on the market.": "Ce nom est en vente sur le marché.",
|
||||||
|
"Pending approvals": "Approbations en attente",
|
||||||
|
"Registrations under this TLD that wait for your co-signature. Approving signs your TLD certificate into the buyer's transaction and broadcasts it; the certificate returns to you in the same transaction. Nothing can be registered under a co-sign or private TLD without this step.": "Enregistrements sous ce TLD qui attendent votre cosignature. Approuver signe votre certificat de TLD dans la transaction de l'acheteur et la diffuse ; le certificat vous revient dans la même transaction. Rien ne peut être enregistré sous un TLD à cosignature ou privé sans cette étape.",
|
||||||
|
"No requests waiting.": "Aucune demande en attente.",
|
||||||
|
"awaiting you": "vous attend",
|
||||||
|
"Approve & broadcast": "Approuver et diffuser",
|
||||||
|
"Decline": "Refuser",
|
||||||
|
"Request approval — sign": "Demander l'approbation — signer",
|
||||||
|
"Withdraw request": "Retirer la demande",
|
||||||
|
"Request declined": "Demande refusée",
|
||||||
|
"Open dashboard →": "Ouvrir le tableau de bord →"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
i18n/pt.json
41
i18n/pt.json
|
|
@ -544,5 +544,44 @@
|
||||||
"Wallet settings": "Definições da carteira",
|
"Wallet settings": "Definições da carteira",
|
||||||
"session, PIN, forget device": "sessão, PIN, esquecer dispositivo",
|
"session, PIN, forget device": "sessão, PIN, esquecer dispositivo",
|
||||||
"Holdings": "Posses",
|
"Holdings": "Posses",
|
||||||
"open the dashboard to load": "abra o painel para carregar"
|
"open the dashboard to load": "abra o painel para carregar",
|
||||||
|
"🛒 Market": "🛒 Mercado",
|
||||||
|
"🛒 Names for sale": "🛒 Nomes à venda",
|
||||||
|
"Market — names for sale on Sirius.X": "Mercado — nomes à venda no Sirius.X",
|
||||||
|
"for sale": "à venda",
|
||||||
|
"Every sale is a single Bitcoin Cash transaction. The seller signed an offer that pays them only when the certificate moves to you — you add your coins, sign, broadcast. No escrow, no middleman, no way for either side to cheat.": "Cada venda é uma única transação Bitcoin Cash. O vendedor assinou uma oferta que só lhe paga quando o certificado passa para si — adiciona as suas moedas, assina, transmite. Sem custódia, sem intermediários, sem forma de alguém fazer batota.",
|
||||||
|
"Sort: price": "Ordenar: preço",
|
||||||
|
"Loading listings…": "A carregar anúncios…",
|
||||||
|
"Selling a name?": "Vai vender um nome?",
|
||||||
|
"Open it in your": "Abra-o no seu",
|
||||||
|
", choose": ", escolha",
|
||||||
|
"Sell": "Vender",
|
||||||
|
", set a price. The listing is a signed offer stored by the gateway; cancelling it moves your certificate once so the old offer can never be used.": ", defina um preço. O anúncio é uma oferta assinada guardada pelo gateway; cancelá-lo move o seu certificado uma vez para que a oferta antiga nunca possa ser usada.",
|
||||||
|
"One transaction: your coins pay the seller, the certificate lands in your wallet, and the registry is told about the new owner. Records stay as they are — you can change them in the dashboard right after.": "Uma transação: as suas moedas pagam ao vendedor, o certificado chega à sua carteira e o registo fica a saber do novo dono. Os registos ficam como estão — pode alterá-los no painel logo a seguir.",
|
||||||
|
"Sign in to buy →": "Iniciar sessão para comprar →",
|
||||||
|
"Buy — sign & broadcast": "Comprar — assinar e transmitir",
|
||||||
|
"Buy →": "Comprar →",
|
||||||
|
"Nothing is for sale right now. Owners list names from their dashboard.": "De momento não há nada à venda. Os donos anunciam nomes a partir do seu painel.",
|
||||||
|
"Sell this name": "Vender este nome",
|
||||||
|
"You sign an offer once: \"whoever pays me this price gets the certificate\". The offer is stored by the gateway and shown on the": "Assina uma oferta uma vez: «quem me pagar este preço fica com o certificado». O gateway guarda a oferta e mostra-a no",
|
||||||
|
"market": "mercado",
|
||||||
|
". A buyer completes it in one transaction — you are paid in the same transaction that moves the name, or not at all. No escrow, nothing to trust.": ". Um comprador completa-a numa transação — recebe na mesma transação que move o nome, ou não recebe de todo. Sem custódia, nada em que confiar.",
|
||||||
|
"Checking the market…": "A verificar o mercado…",
|
||||||
|
"Price (USD)": "Preço (USD)",
|
||||||
|
"Price (sat)": "Preço (sat)",
|
||||||
|
"List for sale →": "Pôr à venda →",
|
||||||
|
"Cancel listing →": "Cancelar anúncio →",
|
||||||
|
"Cancelling removes the offer from the market and moves your certificate once (a no-op update, a few cents), so the signed offer can never be completed later.": "Cancelar retira a oferta do mercado e move o seu certificado uma vez (uma atualização vazia, alguns cêntimos), para que a oferta assinada nunca possa ser concluída mais tarde.",
|
||||||
|
"Not for sale. Set a price to list it.": "Não está à venda. Defina um preço para o anunciar.",
|
||||||
|
"This name is listed on the market.": "Este nome está anunciado no mercado.",
|
||||||
|
"Pending approvals": "Aprovações pendentes",
|
||||||
|
"Registrations under this TLD that wait for your co-signature. Approving signs your TLD certificate into the buyer's transaction and broadcasts it; the certificate returns to you in the same transaction. Nothing can be registered under a co-sign or private TLD without this step.": "Registos sob este TLD que aguardam a sua coassinatura. Aprovar assina o seu certificado de TLD na transação do comprador e transmite-a; o certificado volta para si na mesma transação. Nada pode ser registado sob um TLD de coassinatura ou privado sem este passo.",
|
||||||
|
"No requests waiting.": "Sem pedidos em espera.",
|
||||||
|
"awaiting you": "à sua espera",
|
||||||
|
"Approve & broadcast": "Aprovar e transmitir",
|
||||||
|
"Decline": "Recusar",
|
||||||
|
"Request approval — sign": "Pedir aprovação — assinar",
|
||||||
|
"Withdraw request": "Retirar pedido",
|
||||||
|
"Request declined": "Pedido recusado",
|
||||||
|
"Open dashboard →": "Abrir painel →"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
41
i18n/ru.json
41
i18n/ru.json
|
|
@ -544,5 +544,44 @@
|
||||||
"Wallet settings": "Настройки кошелька",
|
"Wallet settings": "Настройки кошелька",
|
||||||
"session, PIN, forget device": "сессия, PIN, забыть устройство",
|
"session, PIN, forget device": "сессия, PIN, забыть устройство",
|
||||||
"Holdings": "Владения",
|
"Holdings": "Владения",
|
||||||
"open the dashboard to load": "откройте панель для загрузки"
|
"open the dashboard to load": "откройте панель для загрузки",
|
||||||
|
"🛒 Market": "🛒 Маркет",
|
||||||
|
"🛒 Names for sale": "🛒 Имена на продажу",
|
||||||
|
"Market — names for sale on Sirius.X": "Маркет — имена на продажу на Sirius.X",
|
||||||
|
"for sale": "на продажу",
|
||||||
|
"Every sale is a single Bitcoin Cash transaction. The seller signed an offer that pays them only when the certificate moves to you — you add your coins, sign, broadcast. No escrow, no middleman, no way for either side to cheat.": "Каждая продажа — одна транзакция Bitcoin Cash. Продавец подписал оферту, по которой он получает деньги только когда сертификат переходит к вам — вы добавляете свои монеты, подписываете, отправляете. Без эскроу, без посредников, без возможности обмана с любой стороны.",
|
||||||
|
"Sort: price": "Сортировка: цена",
|
||||||
|
"Loading listings…": "Загрузка объявлений…",
|
||||||
|
"Selling a name?": "Продаёте имя?",
|
||||||
|
"Open it in your": "Откройте его в своей",
|
||||||
|
", choose": ", выберите",
|
||||||
|
"Sell": "Продать",
|
||||||
|
", set a price. The listing is a signed offer stored by the gateway; cancelling it moves your certificate once so the old offer can never be used.": ", укажите цену. Объявление — это подписанная оферта, которую хранит шлюз; при отмене ваш сертификат перемещается один раз, чтобы старую оферту нельзя было использовать.",
|
||||||
|
"One transaction: your coins pay the seller, the certificate lands in your wallet, and the registry is told about the new owner. Records stay as they are — you can change them in the dashboard right after.": "Одна транзакция: ваши монеты оплачивают продавца, сертификат попадает в ваш кошелёк, а реестр узнаёт о новом владельце. Записи остаются прежними — вы можете изменить их в панели сразу после.",
|
||||||
|
"Sign in to buy →": "Войти, чтобы купить →",
|
||||||
|
"Buy — sign & broadcast": "Купить — подписать и отправить",
|
||||||
|
"Buy →": "Купить →",
|
||||||
|
"Nothing is for sale right now. Owners list names from their dashboard.": "Сейчас ничего не продаётся. Владельцы выставляют имена из своей панели.",
|
||||||
|
"Sell this name": "Продать это имя",
|
||||||
|
"You sign an offer once: \"whoever pays me this price gets the certificate\". The offer is stored by the gateway and shown on the": "Вы один раз подписываете оферту: «кто заплатит мне эту цену, получит сертификат». Шлюз хранит оферту и показывает её на",
|
||||||
|
"market": "маркете",
|
||||||
|
". A buyer completes it in one transaction — you are paid in the same transaction that moves the name, or not at all. No escrow, nothing to trust.": ". Покупатель завершает её одной транзакцией — вы получаете оплату в той же транзакции, которая передаёт имя, либо не получаете вовсе. Без эскроу, доверять никому не нужно.",
|
||||||
|
"Checking the market…": "Проверяем маркет…",
|
||||||
|
"Price (USD)": "Цена (USD)",
|
||||||
|
"Price (sat)": "Цена (сат)",
|
||||||
|
"List for sale →": "Выставить на продажу →",
|
||||||
|
"Cancel listing →": "Снять с продажи →",
|
||||||
|
"Cancelling removes the offer from the market and moves your certificate once (a no-op update, a few cents), so the signed offer can never be completed later.": "Отмена убирает оферту с маркета и один раз перемещает ваш сертификат (пустое обновление, несколько центов), чтобы подписанную оферту нельзя было исполнить позже.",
|
||||||
|
"Not for sale. Set a price to list it.": "Не продаётся. Укажите цену, чтобы выставить.",
|
||||||
|
"This name is listed on the market.": "Это имя выставлено на маркете.",
|
||||||
|
"Pending approvals": "Ожидают одобрения",
|
||||||
|
"Registrations under this TLD that wait for your co-signature. Approving signs your TLD certificate into the buyer's transaction and broadcasts it; the certificate returns to you in the same transaction. Nothing can be registered under a co-sign or private TLD without this step.": "Регистрации под этим TLD, ожидающие вашей подписи. Одобрение подписывает ваш сертификат TLD в транзакции покупателя и отправляет её; сертификат возвращается к вам в той же транзакции. Без этого шага под TLD с со-подписью или приватным TLD ничего зарегистрировать нельзя.",
|
||||||
|
"No requests waiting.": "Запросов нет.",
|
||||||
|
"awaiting you": "ждёт вас",
|
||||||
|
"Approve & broadcast": "Одобрить и отправить",
|
||||||
|
"Decline": "Отклонить",
|
||||||
|
"Request approval — sign": "Запросить одобрение — подписать",
|
||||||
|
"Withdraw request": "Отозвать запрос",
|
||||||
|
"Request declined": "Запрос отклонён",
|
||||||
|
"Open dashboard →": "Открыть панель →"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,7 +130,7 @@
|
||||||
.verify dd{margin:2px 0 0;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-all;color:var(--ink)}
|
.verify dd{margin:2px 0 0;font-family:ui-monospace,monospace;font-size:12.5px;word-break:break-all;color:var(--ink)}
|
||||||
footer{border-top:1px solid var(--line);padding:2.4rem 0 3.5rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
footer{border-top:1px solid var(--line);padding:2.4rem 0 3.5rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
||||||
</style>
|
</style>
|
||||||
<script src="./js/i18n.js?v=20260917g"></script>
|
<script src="./js/i18n.js?v=20260917h"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -139,6 +139,7 @@
|
||||||
<a href="#try">Try it</a>
|
<a href="#try">Try it</a>
|
||||||
<a href="./#search-input">✏️ Register</a>
|
<a href="./#search-input">✏️ Register</a>
|
||||||
<a href="./tld.html">🌐 TLD</a>
|
<a href="./tld.html">🌐 TLD</a>
|
||||||
|
<a href="./market.html">🛒 Market</a>
|
||||||
<a href="./theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
|
<a href="./theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
|
||||||
<a href="./portal.html" class="portal">🔑 Sign in</a>
|
<a href="./portal.html" class="portal">🔑 Sign in</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -507,13 +508,13 @@ process.exit(0);"</pre>
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||||
</script>
|
</script>
|
||||||
<script type="module" src="./js/register-flow.js?v=20260917dash"></script>
|
<script type="module" src="./js/register-flow.js?v=20260917market"></script>
|
||||||
|
|
||||||
<script src="./js/pricing.js?v=20260916private"></script>
|
<script src="./js/pricing.js?v=20260917market"></script>
|
||||||
<script src="./js/session.js?v=20260917dash"></script>
|
<script src="./js/session.js?v=20260917dash"></script>
|
||||||
<script src="./js/pin-escrow.js?v=20260909pin"></script>
|
<script src="./js/pin-escrow.js?v=20260909pin"></script>
|
||||||
<script defer src="./js/theme.js?v=20260910font"></script>
|
<script defer src="./js/theme.js?v=20260910font"></script>
|
||||||
<script defer src="./js/site-footer.js?v=20260907rel"></script>
|
<script defer src="./js/site-footer.js?v=20260917market"></script>
|
||||||
<script defer src="./js/profile-menu.js?v=20260917dash"></script>
|
<script defer src="./js/profile-menu.js?v=20260917dash"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
123
js/dashboard.js
123
js/dashboard.js
|
|
@ -10,7 +10,7 @@
|
||||||
// is the persistent device session (js/session.js): sign in once, come back
|
// is the persistent device session (js/session.js): sign in once, come back
|
||||||
// without a prompt until you sign out.
|
// without a prompt until you sign out.
|
||||||
|
|
||||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917sig";
|
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market";
|
||||||
|
|
||||||
const API = "https://silentmode.st";
|
const API = "https://silentmode.st";
|
||||||
const TLD_API = "https://navigate.st/api/tlds?include_hidden=1";
|
const TLD_API = "https://navigate.st/api/tlds?include_hidden=1";
|
||||||
|
|
@ -364,6 +364,8 @@ async function openDomain(name) {
|
||||||
$("tr-addr").value = ""; $("tr-ack").checked = false; $("tr-send").disabled = true; setMsg("tr-msg", "");
|
$("tr-addr").value = ""; $("tr-ack").checked = false; $("tr-send").disabled = true; setMsg("tr-msg", "");
|
||||||
setMsg("dns-msg", ""); setMsg("ct-msg", ""); setMsg("rd-msg", "");
|
setMsg("dns-msg", ""); setMsg("ct-msg", ""); setMsg("rd-msg", "");
|
||||||
setSub(pendingSub || "summary"); pendingSub = "summary";
|
setSub(pendingSub || "summary"); pendingSub = "summary";
|
||||||
|
$("sell-log").className = "steps-log"; $("sell-log").innerHTML = ""; setMsg("sell-msg", "");
|
||||||
|
loadListing(name);
|
||||||
await loadDns(name, true);
|
await loadDns(name, true);
|
||||||
}
|
}
|
||||||
function setMsg(id, text, cls = "") { const m = $(id); if (!m) return; m.textContent = text; m.className = "msg " + cls; }
|
function setMsg(id, text, cls = "") { const m = $(id); if (!m) return; m.textContent = text; m.className = "msg " + cls; }
|
||||||
|
|
@ -610,6 +612,81 @@ $("tr-send").addEventListener("click", async () => {
|
||||||
finally { try { el?.close?.(); } catch {} }
|
finally { try { el?.close?.(); } catch {} }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ---------- signed API calls (owner proves control of the name's key) ----------
|
||||||
|
async function signedHeaders(msg) {
|
||||||
|
const ts = String(Date.now());
|
||||||
|
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(msg.replace("{ts}", ts))));
|
||||||
|
const sig = wallet.signMessage(digest);
|
||||||
|
return { "x-bns-ts": ts, "x-bns-sig": btoa(String.fromCharCode(...sig)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- sell (marketplace listing) ----------
|
||||||
|
const RATE = () => window.siriusPricing?.CHIPNET_SATS_PER_USD || 250000;
|
||||||
|
let activeListing = null;
|
||||||
|
function paintSell() {
|
||||||
|
const st = $("sell-status");
|
||||||
|
$("sell-rate").textContent = `rate ${fmtInt(RATE())} sat/USD (chipnet)`;
|
||||||
|
if (activeListing) {
|
||||||
|
st.className = "status ok"; st.textContent = "This name is listed on the market.";
|
||||||
|
$("sell-form").classList.add("hidden"); $("sell-active").classList.remove("hidden");
|
||||||
|
const p = BigInt(activeListing.price_sats);
|
||||||
|
$("sell-kv").innerHTML = [["Price", `${fmtInt(p)} sat (≈ ${esc(fmtUsd(Math.round(Number(p) / RATE() * 100) / 100))})`], ["Listed", esc(new Date(activeListing.created_at).toLocaleString())], ["Offer", `<code>${esc(activeListing.partial_tx.slice(0, 40))}…</code>`], ["Market page", `<a href="./market.html" target="_blank" rel="noopener">open →</a>`]]
|
||||||
|
.map(([k, v]) => `<div class="k">${k}</div><div class="v">${v}</div>`).join("");
|
||||||
|
} else {
|
||||||
|
st.className = "status"; st.textContent = "Not for sale. Set a price to list it.";
|
||||||
|
$("sell-form").classList.remove("hidden"); $("sell-active").classList.add("hidden");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function loadListing(name) {
|
||||||
|
activeListing = null;
|
||||||
|
try { const r = await fetch(`${API}/api/market/${encodeURIComponent(name)}`, { cache: "no-store" }); if (r.ok) activeListing = await r.json(); } catch {}
|
||||||
|
if (current.name === name) paintSell();
|
||||||
|
}
|
||||||
|
$("sell-usd").addEventListener("input", () => { const v = Number(String($("sell-usd").value).replace(/[^\d.]/g, "")); $("sell-sats").value = v > 0 ? String(Math.round(v * RATE())) : ""; });
|
||||||
|
$("sell-sats").addEventListener("input", () => { const s = Number(String($("sell-sats").value).replace(/\D/g, "")); $("sell-usd").value = s > 0 ? String(Math.round(s / RATE() * 100) / 100) : ""; });
|
||||||
|
$("sell-list").addEventListener("click", async () => {
|
||||||
|
if (!current.entry || !wallet) return;
|
||||||
|
if (wallet.source === "wc") return setMsg("sell-msg", "Listing from an external wallet lands in the next drop — sign in with the built-in wallet.", "err");
|
||||||
|
const sats = BigInt(Number(String($("sell-sats").value).replace(/\D/g, "")) || 0);
|
||||||
|
if (sats < 10000n) return setMsg("sell-msg", "Set a price of at least 10,000 sat.", "err");
|
||||||
|
const push = logger("sell-log"); const btn = $("sell-list"); btn.disabled = true; btn.textContent = "Signing…"; setMsg("sell-msg", "");
|
||||||
|
let el = null;
|
||||||
|
try {
|
||||||
|
el = await BNS.connect(); push("connected to chipnet");
|
||||||
|
const utxos = await BNS.getUtxosForAddresses(el, wallet.watchedAddresses);
|
||||||
|
const raw = BNS.normalizeName(current.name);
|
||||||
|
const commitment = [...new TextEncoder().encode(raw)].map((b) => b.toString(16).padStart(2, "0")).join("");
|
||||||
|
const cert = utxos.find((u) => u.token?.category === current.entry.category && u.token?.nft?.commitment === commitment);
|
||||||
|
if (!cert) throw new Error("certificate UTXO not found in this wallet");
|
||||||
|
push("found certificate · signing the offer (SINGLE|ANYONECANPAY)");
|
||||||
|
const listing = BNS.buildListing({ name: current.name, certificateUtxo: cert, priceSats: sats, sellerAddress: cert.address, addressToLockingBytecode: BNS.addressToLockingBytecode, keyFor: (h) => wallet.keyFor(h) });
|
||||||
|
const r = await fetch(`${API}/api/market`, { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify(listing) });
|
||||||
|
const j = await r.json().catch(() => ({}));
|
||||||
|
if (!r.ok) throw new Error(j.error || `API ${r.status}`);
|
||||||
|
push("listed on the market");
|
||||||
|
activeListing = j.listing; paintSell();
|
||||||
|
setMsg("sell-msg", `Listed for ${fmtInt(sats)} sat. Nothing moves until a buyer pays that price.`, "ok");
|
||||||
|
} catch (e) { setMsg("sell-msg", "Failed: " + (e.message || e), "err"); push("error: " + (e.message || e)); }
|
||||||
|
finally { btn.disabled = false; btn.textContent = "List for sale →"; try { el?.close?.(); } catch {} }
|
||||||
|
});
|
||||||
|
$("sell-cancel").addEventListener("click", async () => {
|
||||||
|
if (!current.entry || !wallet || !activeListing) return;
|
||||||
|
if (!confirm(`Cancel the listing for ${current.name}? This also moves the certificate once so the signed offer becomes void.`)) return;
|
||||||
|
const push = logger("sell-log"); const btn = $("sell-cancel"); btn.disabled = true; btn.textContent = "Cancelling…"; setMsg("sell-msg", "");
|
||||||
|
try {
|
||||||
|
const h = await signedHeaders(`BNS-MARKET1\n${current.name}\n{ts}`);
|
||||||
|
const r = await fetch(`${API}/api/market/${encodeURIComponent(current.name)}`, { method: "DELETE", headers: h });
|
||||||
|
const j = await r.json().catch(() => ({}));
|
||||||
|
if (!r.ok) throw new Error(j.error || `API ${r.status}`);
|
||||||
|
push("removed from the market");
|
||||||
|
activeListing = null; paintSell();
|
||||||
|
push("invalidating the signed offer on chain (no-op update)…");
|
||||||
|
const ok = await publishRecords(current.entry.records || {}, "sell-log", "sell-msg", "sell-cancel", "Listing cancelled and the offer voided on chain.");
|
||||||
|
if (!ok) setMsg("sell-msg", "Removed from the market, but the on-chain invalidation failed — retry from Content & hosting (Save records) so the old offer cannot be used.", "err");
|
||||||
|
} catch (e) { setMsg("sell-msg", "Failed: " + (e.message || e), "err"); push("error: " + (e.message || e)); }
|
||||||
|
finally { btn.disabled = false; btn.textContent = "Cancel listing →"; }
|
||||||
|
});
|
||||||
|
|
||||||
// ---------- export ----------
|
// ---------- export ----------
|
||||||
function zoneText() {
|
function zoneText() {
|
||||||
const e = current.entry; if (!e) return "";
|
const e = current.entry; if (!e) return "";
|
||||||
|
|
@ -699,6 +776,7 @@ async function openTld(label) {
|
||||||
["Policy", esc(rec.policy || "open") + (rec.min_len ? ` · min length ${rec.min_len}` : "") + (rec.reserved ? ` · reserved: ${esc(rec.reserved)}` : "")],
|
["Policy", esc(rec.policy || "open") + (rec.min_len ? ` · min length ${rec.min_len}` : "") + (rec.reserved ? ` · reserved: ${esc(rec.reserved)}` : "")],
|
||||||
];
|
];
|
||||||
$("td-kv").innerHTML = kv.map(([k, v]) => `<div class="k">${k}</div><div class="v">${v}</div>`).join("");
|
$("td-kv").innerHTML = kv.map(([k, v]) => `<div class="k">${k}</div><div class="v">${v}</div>`).join("");
|
||||||
|
loadCosign(label);
|
||||||
const names = (await registry()).filter((n) => n.tld === label);
|
const names = (await registry()).filter((n) => n.tld === label);
|
||||||
$("td-names").innerHTML = names.length
|
$("td-names").innerHTML = names.length
|
||||||
? names.map((n) => `<div class="drow" style="cursor:default"><div class="n"><span>${esc(n.name)}</span>${H.names.some((m) => m.name === n.name) ? '<span class="badge b-x">yours</span>' : ""}</div>
|
? names.map((n) => `<div class="drow" style="cursor:default"><div class="n"><span>${esc(n.name)}</span>${H.names.some((m) => m.name === n.name) ? '<span class="badge b-x">yours</span>' : ""}</div>
|
||||||
|
|
@ -706,6 +784,49 @@ async function openTld(label) {
|
||||||
<div class="meta"><span>block ${n.height ? fmtInt(n.height) : "mempool"}</span></div></div>`).join("")
|
<div class="meta"><span>block ${n.height ? fmtInt(n.height) : "mempool"}</span></div></div>`).join("")
|
||||||
: `<div class="empty">No names registered under .${esc(label)} yet.</div>`;
|
: `<div class="empty">No names registered under .${esc(label)} yet.</div>`;
|
||||||
}
|
}
|
||||||
|
// ---------- co-sign queue (registrations under this TLD awaiting the owner) ----------
|
||||||
|
async function loadCosign(label) {
|
||||||
|
const box = $("td-cosign");
|
||||||
|
box.innerHTML = `<div class="empty">Checking…</div>`;
|
||||||
|
let list = [];
|
||||||
|
try { const j = await (await fetch(`${API}/api/cosign?tld=${encodeURIComponent(label)}`, { cache: "no-store" })).json(); list = j.requests || []; } catch {}
|
||||||
|
if (currentTld?.tld !== label) return;
|
||||||
|
box.innerHTML = list.length
|
||||||
|
? list.map((r) => `<div class="drow" style="cursor:default"><div class="n"><span>${esc(r.name)}</span><span class="badge b-warn">awaiting you</span></div>
|
||||||
|
<div class="acts"><button class="btn small acid" data-approve="${esc(r.id)}">Approve & broadcast</button><button class="btn small danger" data-decline="${esc(r.id)}">Decline</button></div>
|
||||||
|
<div class="meta"><span>buyer ${esc(r.buyer.replace(/^[^:]+:/, "").slice(0, 10))}…</span><span>·</span><span>${esc(new Date(r.created_at).toLocaleString())}</span>${r.note ? `<span>·</span><span>“${esc(r.note)}”</span>` : ""}</div></div>`).join("")
|
||||||
|
: `<div class="empty">No requests waiting.</div>`;
|
||||||
|
}
|
||||||
|
$("td-cosign").addEventListener("click", async (e) => {
|
||||||
|
const a = e.target.closest("[data-approve]"), d = e.target.closest("[data-decline]");
|
||||||
|
if (!a && !d) return;
|
||||||
|
if (!currentTld || !wallet) return;
|
||||||
|
if (wallet.source === "wc") return setMsg("td-cosign-msg", "Co-signing from an external wallet lands in the next drop — sign in with the built-in wallet.", "err");
|
||||||
|
const id = (a || d).dataset.approve || (a || d).dataset.decline;
|
||||||
|
const btn = a || d; btn.disabled = true; setMsg("td-cosign-msg", "");
|
||||||
|
let el = null;
|
||||||
|
try {
|
||||||
|
const h = await signedHeaders(`BNS-COSIGN1
|
||||||
|
${id}
|
||||||
|
{ts}`);
|
||||||
|
if (a) {
|
||||||
|
const r = await fetch(`${API}/api/cosign/${encodeURIComponent(id)}`, { cache: "no-store" });
|
||||||
|
if (!r.ok) throw new Error("request is gone (withdrawn or expired)");
|
||||||
|
const req = await r.json();
|
||||||
|
const signed = BNS.signCosignRequest(req, (lock) => wallet.keyFor(lock));
|
||||||
|
if (!signed.signedInputs) throw new Error("this wallet does not hold the certificate input of that transaction");
|
||||||
|
el = await BNS.connect();
|
||||||
|
const txid = await BNS.broadcast(el, signed.hex);
|
||||||
|
setMsg("td-cosign-msg", `Approved — ${req.name} registered in ${txid.slice(0, 16)}…. Your TLD certificate came back to you in the same transaction.`, "ok");
|
||||||
|
}
|
||||||
|
const del = await fetch(`${API}/api/cosign/${encodeURIComponent(id)}`, { method: "DELETE", headers: h });
|
||||||
|
if (!del.ok && d) throw new Error(`API ${del.status}`);
|
||||||
|
if (d) setMsg("td-cosign-msg", "Declined — the request was removed. The buyer's coins never left their wallet.", "ok");
|
||||||
|
await loadCosign(currentTld.tld);
|
||||||
|
if (a) { registryCache = { at: 0, names: [] }; openTld(currentTld.tld); }
|
||||||
|
} catch (err) { setMsg("td-cosign-msg", "Failed: " + (err.message || err), "err"); btn.disabled = false; }
|
||||||
|
finally { try { el?.close?.(); } catch {} }
|
||||||
|
});
|
||||||
$("td-register").addEventListener("click", () => {
|
$("td-register").addEventListener("click", () => {
|
||||||
if (!currentTld) return;
|
if (!currentTld) return;
|
||||||
const input = $("td-label"); const label = String(input.value || "").toLowerCase().replace(/[^a-z0-9-]/g, "");
|
const input = $("td-label"); const label = String(input.value || "").toLowerCase().replace(/[^a-z0-9-]/g, "");
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@
|
||||||
(function () {
|
(function () {
|
||||||
var STORE = "sirius-lang";
|
var STORE = "sirius-lang";
|
||||||
var CACHE = "sirius-i18n:";
|
var CACHE = "sirius-i18n:";
|
||||||
var VERSION = "20260917f"; // bump when dictionaries change
|
var VERSION = "20260917h"; // bump when dictionaries change
|
||||||
var LANGS = {
|
var LANGS = {
|
||||||
en: "English",
|
en: "English",
|
||||||
de: "Deutsch",
|
de: "Deutsch",
|
||||||
|
|
|
||||||
122
js/market.js
Normal file
122
js/market.js
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
// Marketplace page: list the gateway's active offers and complete a purchase.
|
||||||
|
//
|
||||||
|
// A listing is the seller's partially signed transaction (see
|
||||||
|
// Argus/src/lib/market-tx.js). Buying = verify it locally, append our own
|
||||||
|
// funding inputs and the outputs that give us the certificate and update
|
||||||
|
// the registry, sign our inputs, broadcast. The seller's signature only
|
||||||
|
// stays valid while their certificate UTXO is unspent, so the gateway prunes
|
||||||
|
// stale offers and the buyer's broadcast is the final arbiter.
|
||||||
|
|
||||||
|
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market";
|
||||||
|
|
||||||
|
const API = "https://silentmode.st";
|
||||||
|
const $ = (id) => document.getElementById(id);
|
||||||
|
const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c]));
|
||||||
|
const fmtInt = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||||
|
const usd = (sats) => { const r = window.siriusPricing?.CHIPNET_SATS_PER_USD || 250000; const v = Number(sats) / r; return (window.siriusPricing?.formatUsd || ((n) => "$" + n.toFixed(2)))(Math.round(v * 100) / 100); };
|
||||||
|
const shortAddr = (a) => (a ? a.replace(/^[^:]+:/, "").slice(0, 8) + "…" + a.slice(-5) : "—");
|
||||||
|
|
||||||
|
let listings = [];
|
||||||
|
let wallet = null;
|
||||||
|
let selected = null;
|
||||||
|
|
||||||
|
async function restoreWallet() {
|
||||||
|
if (window.siriusWallet) return window.siriusWallet;
|
||||||
|
const S = window.siriusSession;
|
||||||
|
if (!S?.restore) return null;
|
||||||
|
try {
|
||||||
|
const s = await S.restore();
|
||||||
|
if (!s?.mnemonic) return null;
|
||||||
|
const w = await BNS.BuiltInWallet.fromMnemonic(s.mnemonic, BNS.CHIPNET_PREFIX, s.accountPath || undefined);
|
||||||
|
window.siriusWallet = w;
|
||||||
|
return w;
|
||||||
|
} catch { return null; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
const status = $("status");
|
||||||
|
try {
|
||||||
|
const r = await fetch(`${API}/api/market`, { cache: "no-store" });
|
||||||
|
if (!r.ok) throw new Error("API " + r.status);
|
||||||
|
const j = await r.json();
|
||||||
|
listings = (j.listings || []).map((l) => ({ ...l, label: l.name.split(".")[0], tld: l.tld || l.name.split(".").pop() }));
|
||||||
|
render();
|
||||||
|
} catch (e) { status.className = "status err"; status.textContent = "Could not load listings: " + (e.message || e); }
|
||||||
|
}
|
||||||
|
function render() {
|
||||||
|
const q = ($("q").value || "").toLowerCase().trim();
|
||||||
|
const sort = $("sort").value;
|
||||||
|
const tldSel = $("tld");
|
||||||
|
const tlds = [...new Set(listings.map((l) => l.tld))].sort();
|
||||||
|
const keep = tldSel.value;
|
||||||
|
tldSel.innerHTML = `<option value="">All TLDs</option>` + tlds.map((t) => `<option value="${esc(t)}"${t === keep ? " selected" : ""}>.${esc(t)}</option>`).join("");
|
||||||
|
let rows = listings.filter((l) => (!q || l.name.includes(q)) && (!tldSel.value || l.tld === tldSel.value));
|
||||||
|
rows.sort((a, b) => sort === "name" ? a.name.localeCompare(b.name) : sort === "new" ? Date.parse(b.created_at) - Date.parse(a.created_at) : Number(a.price_sats) - Number(b.price_sats));
|
||||||
|
$("count").textContent = `${rows.length} of ${listings.length}`;
|
||||||
|
const status = $("status");
|
||||||
|
if (!listings.length) { status.className = "status"; status.textContent = "Nothing is for sale right now. Owners list names from their dashboard."; $("list").innerHTML = ""; return; }
|
||||||
|
status.className = "status"; status.textContent = `${listings.length} name${listings.length === 1 ? "" : "s"} for sale. Prices are set by the sellers; you pay the price plus about 1,300 sat of chain dust and fee.`;
|
||||||
|
$("list").innerHTML = rows.length ? rows.map((l) => `
|
||||||
|
<div class="row">
|
||||||
|
<div class="n">${esc(l.label)}.<span class="tld">${esc(l.tld)}</span> ${wallet && l.seller === wallet.address ? '<span class="badge">yours</span>' : ""}</div>
|
||||||
|
<div class="price"><b>${fmtInt(l.price_sats)} sat</b><span>≈ ${esc(usd(l.price_sats))}</span></div>
|
||||||
|
<div><button class="btn acid small" data-buy="${esc(l.name)}" ${wallet && l.seller === wallet.address ? "disabled" : ""}>Buy →</button></div>
|
||||||
|
<div class="meta"><span>seller ${esc(shortAddr(l.seller))}</span><span>·</span><span>listed ${esc(new Date(l.created_at).toLocaleDateString())}</span><span>·</span><a href="${API}/bns/${encodeURIComponent(l.name)}/" target="_blank" rel="noopener">view site →</a></div>
|
||||||
|
</div>`).join("") : `<div class="empty">Nothing matches that filter.</div>`;
|
||||||
|
}
|
||||||
|
["q", "sort", "tld"].forEach((id) => $(id).addEventListener("input", render));
|
||||||
|
document.addEventListener("click", (e) => { const b = e.target.closest("[data-buy]"); if (b) openBuy(b.dataset.buy); });
|
||||||
|
|
||||||
|
// ---------- buy ----------
|
||||||
|
function setMsg(t, cls = "") { $("buy-msg").textContent = t; $("buy-msg").className = "msg " + cls; }
|
||||||
|
function push(t) { const log = $("buy-steps"); log.className = "steps on"; const d = document.createElement("div"); d.textContent = t; log.appendChild(d); log.scrollTop = 1e6; }
|
||||||
|
function openBuy(name) {
|
||||||
|
selected = listings.find((l) => l.name === name); if (!selected) return;
|
||||||
|
$("buy-name").textContent = name;
|
||||||
|
const price = BigInt(selected.price_sats);
|
||||||
|
const check = BNS.verifyListing(selected, { category: selected.category });
|
||||||
|
$("buy-kv").innerHTML = [
|
||||||
|
["Price", `${fmtInt(price)} sat (≈ ${esc(usd(price))})`],
|
||||||
|
["Seller", esc(selected.seller)],
|
||||||
|
["You receive", "the name's certificate, to your token address"],
|
||||||
|
["Offer check", check.ok ? "seller signature valid" : "INVALID: " + esc(check.error)],
|
||||||
|
["Extra", "≈ 1,600 sat dust + fee"],
|
||||||
|
].map(([k, v]) => `<div class="k">${k}</div><div class="v">${v}</div>`).join("");
|
||||||
|
$("buy-steps").className = "steps"; $("buy-steps").innerHTML = ""; setMsg("");
|
||||||
|
const canBuy = !!wallet && check.ok && wallet.address !== selected.seller;
|
||||||
|
$("buy-go").disabled = !canBuy; $("buy-go").textContent = "Buy — sign & broadcast";
|
||||||
|
$("buy-signin").hidden = !!wallet;
|
||||||
|
if (!wallet) setMsg("Sign in on the dashboard first — the purchase is signed with your wallet in this browser.");
|
||||||
|
$("buy").classList.add("open");
|
||||||
|
}
|
||||||
|
const closeBuy = () => $("buy").classList.remove("open");
|
||||||
|
$("buy-close").addEventListener("click", closeBuy); $("buy-cancel").addEventListener("click", closeBuy);
|
||||||
|
$("buy").addEventListener("click", (e) => { if (e.target === $("buy")) closeBuy(); });
|
||||||
|
$("buy-go").addEventListener("click", async () => {
|
||||||
|
if (!selected || !wallet) return;
|
||||||
|
const btn = $("buy-go"); btn.disabled = true; btn.textContent = "Buying…"; setMsg("");
|
||||||
|
let el = null;
|
||||||
|
try {
|
||||||
|
el = await BNS.connect(); push("connected to chipnet");
|
||||||
|
const utxos = await BNS.getUtxosForAddresses(el, wallet.watchedAddresses);
|
||||||
|
push(`wallet has ${utxos.filter((u) => !u.token).length} spendable coin(s)`);
|
||||||
|
const built = BNS.completeSale({ listing: selected, buyerAddress: wallet.address, buyerTokenAddress: wallet.tokenAddress, utxos, records: selected.records || {}, addressToLockingBytecode: BNS.addressToLockingBytecode });
|
||||||
|
push(`built sale · price ${fmtInt(built.costs.priceSats)} sat · fee ${built.costs.chainFeeSats} sat`);
|
||||||
|
const signed = BNS.signInputs(built.transaction, built.sourceOutputs, (h) => wallet.keyFor(h), { skip: [built.sellerInputIndex] });
|
||||||
|
push("signed your inputs (the seller's signature is already in place)");
|
||||||
|
const txid = await BNS.broadcast(el, signed.hex);
|
||||||
|
push("broadcast: " + txid);
|
||||||
|
setMsg(`Done. ${selected.name} is yours — it appears in your dashboard within a minute.`, "ok");
|
||||||
|
btn.textContent = "Bought ✓";
|
||||||
|
setTimeout(load, 3000);
|
||||||
|
} catch (e) {
|
||||||
|
const raw = String(e.message || e);
|
||||||
|
setMsg(/not enough funds/i.test(raw) ? `Not enough coins: ${raw}. Fund ${wallet.address} first.` : "Failed: " + raw, "err");
|
||||||
|
push("error: " + raw); btn.disabled = false; btn.textContent = "Buy — sign & broadcast";
|
||||||
|
} finally { try { el?.close?.(); } catch {} }
|
||||||
|
});
|
||||||
|
|
||||||
|
(async function boot() {
|
||||||
|
wallet = await restoreWallet();
|
||||||
|
await load();
|
||||||
|
})();
|
||||||
|
|
@ -48,7 +48,13 @@
|
||||||
hidden,
|
hidden,
|
||||||
hiddenBy: row && row.hidden_by ? row.hidden_by : (hidden ? "owner" : null),
|
hiddenBy: row && row.hidden_by ? row.hidden_by : (hidden ? "owner" : null),
|
||||||
frozen: policy === "frozen",
|
frozen: policy === "frozen",
|
||||||
|
// cosign: anyone may register, but the TLD owner must co-sign the
|
||||||
|
// registration (indexers reject a REG under a cosign/hidden TLD that
|
||||||
|
// does not carry the TLD certificate). hidden implies the same rule
|
||||||
|
// on chain; in the UI hidden is owner-only, cosign is request-based.
|
||||||
|
cosign: policy === "cosign",
|
||||||
policy,
|
policy,
|
||||||
|
category: row && row.category ? row.category : null,
|
||||||
owner: row && row.owner ? row.owner : null,
|
owner: row && row.owner ? row.owner : null,
|
||||||
records: rec,
|
records: rec,
|
||||||
};
|
};
|
||||||
|
|
@ -122,6 +128,9 @@
|
||||||
const owner = isTldOwner(tld, addresses);
|
const owner = isTldOwner(tld, addresses);
|
||||||
if (p.frozen) return { ok: false, reason: "frozen", policy: p, owner };
|
if (p.frozen) return { ok: false, reason: "frozen", policy: p, owner };
|
||||||
if (p.hidden && !owner) return { ok: false, reason: "hidden", policy: p, owner };
|
if (p.hidden && !owner) return { ok: false, reason: "hidden", policy: p, owner };
|
||||||
|
// Sellable, but the registration needs the owner's co-signature: the
|
||||||
|
// flow builds the request and parks it in the gateway's approval queue.
|
||||||
|
if (p.cosign && !owner) return { ok: true, reason: "cosign", policy: p, owner };
|
||||||
return { ok: true, reason: null, policy: p, owner };
|
return { ok: true, reason: null, policy: p, owner };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
// Injects its own modal HTML into <body> and its own CSS into <head> on load
|
// Injects its own modal HTML into <body> and its own CSS into <head> on load
|
||||||
// so the host page needs nothing but a single script include.
|
// so the host page needs nothing but a single script include.
|
||||||
|
|
||||||
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917sig";
|
import * as BNS from "https://silentmode.st/js/bns-register.js?v=20260917market";
|
||||||
|
|
||||||
const $ = (id) => document.getElementById(id);
|
const $ = (id) => document.getElementById(id);
|
||||||
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&":"&","<":"<",">":">",'"':""","'":"'" }[c]));
|
const esc = (s) => String(s).replace(/[&<>"']/g, (c) => ({ "&":"&","<":"<",">":">",'"':""","'":"'" }[c]));
|
||||||
|
|
@ -416,6 +416,14 @@ async function startFlow(name, opts = {}) {
|
||||||
}
|
}
|
||||||
const p = sp?.priceForName(label, tld);
|
const p = sp?.priceForName(label, tld);
|
||||||
state.ownerMint = !!sell.owner;
|
state.ownerMint = !!sell.owner;
|
||||||
|
// Co-sign rule (consensus): a REG under a TLD whose policy is `cosign` or
|
||||||
|
// `hidden` is only admitted if the transaction also carries the TLD's own
|
||||||
|
// certificate. Owner: add it from this wallet. Anyone else under a cosign
|
||||||
|
// TLD: build the transaction, sign our inputs, and park it in the gateway
|
||||||
|
// queue for the owner to co-sign and broadcast.
|
||||||
|
const pol = sell.policy || {};
|
||||||
|
state.cosign = sell.owner && (pol.hidden || pol.cosign) && pol.category ? { tld, category: pol.category } : null;
|
||||||
|
state.cosignRequest = sell.reason === "cosign" && pol.category ? { tld, category: pol.category, owner: pol.owner } : null;
|
||||||
// Owner registering under their own TLD: they would receive 90% of the
|
// Owner registering under their own TLD: they would receive 90% of the
|
||||||
// fee from themselves, so only the platform's share is charged. The
|
// fee from themselves, so only the platform's share is charged. The
|
||||||
// registrar lib already routes the whole fee to the platform when the
|
// registrar lib already routes the whole fee to the platform when the
|
||||||
|
|
@ -610,6 +618,13 @@ async function stepExternalConfirm(session, label) {
|
||||||
// TLD external path: keep the session, hand off to the TLD confirm/mint
|
// TLD external path: keep the session, hand off to the TLD confirm/mint
|
||||||
// steps which know to call registerTldWithExternalWallet.
|
// steps which know to call registerTldWithExternalWallet.
|
||||||
if (state.tld) { state.session = session; return stepConfirmTld(); }
|
if (state.tld) { state.session = session; return stepConfirmTld(); }
|
||||||
|
if (state.cosign || state.cosignRequest) {
|
||||||
|
render(`<h3>Use the built-in wallet for this TLD</h3>
|
||||||
|
<p class="sub">Names under <b>.${esc(state.cosignRequest?.tld || state.cosign?.tld)}</b> need the TLD owner's co-signature in the same transaction. That flow is built for the browser wallet; external-wallet support lands in the next drop.</p>
|
||||||
|
<div class="row end"><button class="btn ghost" id="back">Back</button></div>`);
|
||||||
|
$("back").onclick = async () => { try { await session.disconnect(); } catch {} stepWallet(); };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const feeOverride = state.nameFeeSats != null
|
const feeOverride = state.nameFeeSats != null
|
||||||
? { serviceFee: { ...(BNS.REGISTRAR?.serviceFee ?? {}), sats: state.nameFeeSats } }
|
? { serviceFee: { ...(BNS.REGISTRAR?.serviceFee ?? {}), sats: state.nameFeeSats } }
|
||||||
|
|
@ -922,15 +937,17 @@ async function stepConfirm() {
|
||||||
<h3>Confirm ${esc(quote.displayName)}</h3>
|
<h3>Confirm ${esc(quote.displayName)}</h3>
|
||||||
<p class="sub">One transaction. It mints the certificate to <b>your</b> address and pays the fees.</p>
|
<p class="sub">One transaction. It mints the certificate to <b>your</b> address and pays the fees.</p>
|
||||||
${state.ownerMint ? `<div class="note">You own this TLD, so this is an <b>owner registration</b>: only the platform's 10% share of the name price is charged — the 90% owner share would just be paid to yourself.</div>` : ""}
|
${state.ownerMint ? `<div class="note">You own this TLD, so this is an <b>owner registration</b>: only the platform's 10% share of the name price is charged — the 90% owner share would just be paid to yourself.</div>` : ""}
|
||||||
|
${state.cosign ? `<div class="note">Private TLD: your <b>.${esc(state.cosign.tld)}</b> certificate is spent and re-issued to you in the same transaction — that is the co-signature the registry requires.</div>` : ""}
|
||||||
|
${state.cosignRequest ? `<div class="note warn"><b>Needs the TLD owner's approval.</b> .${esc(state.cosignRequest.tld)} is a co-sign TLD. You sign your part now; the owner co-signs and broadcasts from their dashboard. Nothing leaves your wallet until they do, and if they decline the request simply expires.</div>` : ""}
|
||||||
<table class="price">${rows}</table>
|
<table class="price">${rows}</table>
|
||||||
<div class="note" style="margin-top:16px">The certificate will be minted to
|
<div class="note" style="margin-top:16px">The certificate will be minted to
|
||||||
<span class="mono">${esc(state.wallet.address)}</span> — your key, your name.
|
<span class="mono">${esc(state.wallet.address)}</span> — your key, your name.
|
||||||
You can set where it points immediately afterwards.</div>
|
You can set where it points immediately afterwards.</div>
|
||||||
<div class="row end"><button class="btn ghost" id="back">Back</button>
|
<div class="row end"><button class="btn ghost" id="back">Back</button>
|
||||||
<button class="btn acid" id="go">Register — sign & broadcast</button></div>
|
<button class="btn acid" id="go">${state.cosignRequest ? "Request approval — sign" : "Register — sign & broadcast"}</button></div>
|
||||||
`);
|
`);
|
||||||
$("back").onclick = stepFund;
|
$("back").onclick = stepFund;
|
||||||
$("go").onclick = () => stepApprove({ feeSats: state.nameFeeSats ?? 0n, kind: "name", back: stepConfirm, next: stepRegister });
|
$("go").onclick = () => stepApprove({ feeSats: state.nameFeeSats ?? 0n, kind: "name", back: stepConfirm, next: state.cosignRequest ? stepRequestCosign : stepRegister });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
render(`<h3>Could not prepare</h3><p class="sub">${esc(e.message)}</p>
|
render(`<h3>Could not prepare</h3><p class="sub">${esc(e.message)}</p>
|
||||||
<div class="row end"><button class="btn ghost" id="back">Back</button></div>`);
|
<div class="row end"><button class="btn ghost" id="back">Back</button></div>`);
|
||||||
|
|
@ -1139,6 +1156,7 @@ async function stepRegister() {
|
||||||
wallet: state.wallet, name: state.name, records: {},
|
wallet: state.wallet, name: state.name, records: {},
|
||||||
onProgress: (s) => say(words[s] ?? s),
|
onProgress: (s) => say(words[s] ?? s),
|
||||||
...(state._feeOverride ?? {}),
|
...(state._feeOverride ?? {}),
|
||||||
|
...(state.cosign ? { cosign: state.cosign } : {}),
|
||||||
});
|
});
|
||||||
state.result = res;
|
state.result = res;
|
||||||
stepDone();
|
stepDone();
|
||||||
|
|
@ -1151,6 +1169,95 @@ async function stepRegister() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- co-sign request (third party under a cosign TLD) ----------
|
||||||
|
// Build the full registration with the TLD owner's certificate as the last
|
||||||
|
// input, sign only our own inputs, hand the half-signed transaction to the
|
||||||
|
// gateway queue. The owner signs the certificate input and broadcasts.
|
||||||
|
const GW = "https://silentmode.st";
|
||||||
|
async function stepRequestCosign() {
|
||||||
|
const cr = state.cosignRequest;
|
||||||
|
render(`<h3>Requesting approval for ${esc(state.name)}</h3>
|
||||||
|
<p class="sub">Building and signing your half of the transaction. Do not close this tab.</p>
|
||||||
|
<div class="steps-log" id="log"></div>`);
|
||||||
|
const log = $("log");
|
||||||
|
const say = (s) => { const d = document.createElement("div"); d.textContent = s; log.appendChild(d); log.scrollTop = log.scrollHeight; };
|
||||||
|
const words = {
|
||||||
|
"checking-availability": "checking the name is still free",
|
||||||
|
"loading-coins": "loading your coins",
|
||||||
|
"preparing-wallet": "preparing your wallet (one-off setup transaction)",
|
||||||
|
"prepared": "wallet prepared",
|
||||||
|
"building": "building the registration with the owner's certificate",
|
||||||
|
"signing": "signing your inputs — in this browser",
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
say(`locating the .${cr.tld} certificate`);
|
||||||
|
const cres = await fetch(`${GW}/api/tld-cert/${encodeURIComponent(cr.tld)}`, { cache: "no-store" });
|
||||||
|
const cert = await cres.json();
|
||||||
|
if (!cres.ok) throw new Error(cert.error || `API ${cres.status}`);
|
||||||
|
const built = await BNS.buildCosignRequest(await client(), {
|
||||||
|
wallet: state.wallet, name: state.name, records: {}, tldCert: cert,
|
||||||
|
onProgress: (s) => say(words[s] ?? s),
|
||||||
|
...(state._feeOverride ?? {}),
|
||||||
|
});
|
||||||
|
say("submitting to the approval queue");
|
||||||
|
const r = await fetch(`${GW}/api/cosign`, { method: "POST", headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ name: built.name, buyer: state.wallet.address, tx: built.tx, sourceOutputs: built.sourceOutputs, note: "" }) });
|
||||||
|
const j = await r.json().catch(() => ({}));
|
||||||
|
if (!r.ok) throw new Error(j.error || `API ${r.status}`);
|
||||||
|
state.cosignId = j.id;
|
||||||
|
stepAwaitCosign();
|
||||||
|
} catch (e) {
|
||||||
|
err(e);
|
||||||
|
const row = document.createElement("div"); row.className = "row end";
|
||||||
|
row.innerHTML = `<button class="btn ghost" id="back">Back</button>`;
|
||||||
|
sheet.appendChild(row);
|
||||||
|
$("back").onclick = stepConfirm;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function stepAwaitCosign() {
|
||||||
|
const cr = state.cosignRequest;
|
||||||
|
render(`
|
||||||
|
<h3>Waiting for the owner of .${esc(cr.tld)}</h3>
|
||||||
|
<p class="sub">Your request <span class="mono">${esc(state.cosignId)}</span> is in the queue. The TLD owner sees it in their dashboard and
|
||||||
|
co-signs and broadcasts it in one click. Your coins stay in your wallet until then — if you spend them elsewhere, the request simply becomes invalid.</p>
|
||||||
|
<div class="note">Requests expire after 7 days. You can close this window; when the owner approves, <b>${esc(state.name)}</b> appears in your dashboard.</div>
|
||||||
|
<div class="status" id="poll">Checking every 20 seconds…</div>
|
||||||
|
<div class="row end"><button class="btn danger" id="withdraw">Withdraw request</button><button class="btn ghost" id="done">Close</button></div>
|
||||||
|
`);
|
||||||
|
let stop = false;
|
||||||
|
$("done").onclick = () => { stop = true; close(); };
|
||||||
|
$("withdraw").onclick = async () => {
|
||||||
|
try {
|
||||||
|
const ts = String(Date.now());
|
||||||
|
const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(`BNS-COSIGN1\n${state.cosignId}\n${ts}`)));
|
||||||
|
const sig = btoa(String.fromCharCode(...state.wallet.signMessage(digest)));
|
||||||
|
await fetch(`${GW}/api/cosign/${encodeURIComponent(state.cosignId)}`, { method: "DELETE", headers: { "x-bns-ts": ts, "x-bns-sig": sig } });
|
||||||
|
} catch {}
|
||||||
|
stop = true; close();
|
||||||
|
};
|
||||||
|
const tick = async () => {
|
||||||
|
if (stop || !document.body.contains($("poll"))) return;
|
||||||
|
try {
|
||||||
|
const r = await fetch(`${GW}/api/cosign/${encodeURIComponent(state.cosignId)}`, { cache: "no-store" });
|
||||||
|
if (r.status === 404) {
|
||||||
|
const avail = await BNS.checkAvailability(await client(), state.name);
|
||||||
|
if (!avail.available) {
|
||||||
|
render(`<h3><span class="ok">✓</span> ${esc(state.name)} is yours</h3>
|
||||||
|
<p class="sub">The owner of .${esc(cr.tld)} co-signed and broadcast your registration. Set records and content from your dashboard.</p>
|
||||||
|
<div class="row end"><a class="btn acid" href="./portal.html#domains">Open dashboard →</a></div>`);
|
||||||
|
} else {
|
||||||
|
render(`<h3>Request declined</h3><p class="sub">The owner of .${esc(cr.tld)} removed your request. Nothing was spent.</p>
|
||||||
|
<div class="row end"><button class="btn ghost" id="done">Close</button></div>`);
|
||||||
|
$("done").onclick = close;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
setTimeout(tick, 20000);
|
||||||
|
};
|
||||||
|
setTimeout(tick, 20000);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- done + point somewhere ----------
|
// ---------- done + point somewhere ----------
|
||||||
function stepDone() {
|
function stepDone() {
|
||||||
const r = state.result;
|
const r = state.result;
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="${s("#search-input")}">✏️ Register a name</a></li>
|
<li><a href="${s("#search-input")}">✏️ Register a name</a></li>
|
||||||
<li><a href="${s("tld.html")}">🌐 Register a TLD</a></li>
|
<li><a href="${s("tld.html")}">🌐 Register a TLD</a></li>
|
||||||
|
<li><a href="${s("market.html")}">🛒 Names for sale</a></li>
|
||||||
<li><a href="${s("portal.html")}">🔑 My names portal</a></li>
|
<li><a href="${s("portal.html")}">🔑 My names portal</a></li>
|
||||||
<li><a href="${s("theseus/")}">🧭 Theseus browser</a></li>
|
<li><a href="${s("theseus/")}">🧭 Theseus browser</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
||||||
130
market.html
Normal file
130
market.html
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Market — names for sale on Sirius.X</title>
|
||||||
|
<meta name="description" content="Buy and sell BCNR names. Every sale is one Bitcoin Cash transaction: the seller is paid in the same transaction that hands you the certificate, or not at all.">
|
||||||
|
<link rel="icon" href="./assets/favicon.svg" type="image/svg+xml">
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,600&family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root{--bg:#050810;--panel:#141a24;--panel2:#0a0f1c;--line:rgba(255,255,255,.06);--sky-mid:#0a0f1c;--sky-rim:#131a2a;
|
||||||
|
--ink:#f1f4fa;--mut:#b8c2d4;--dim:#6a7488;--acid:#d6ff3d;--ok:#4fd1a5;--taken:#f6768a;--warn:#ffc75f;
|
||||||
|
--serif:'Fraunces',Georgia,serif;--sans:'DM Sans',system-ui,sans-serif;--mono:'JetBrains Mono',ui-monospace,monospace}
|
||||||
|
*{box-sizing:border-box}
|
||||||
|
body{margin:0;background:radial-gradient(ellipse 1200px 600px at 50% -5%,var(--sky-rim) 0%,transparent 60%),var(--bg);background-attachment:fixed;color:var(--ink);font:15px/1.55 var(--sans);min-height:100vh}
|
||||||
|
h1,h2,h3{font-family:var(--serif)}
|
||||||
|
a{color:var(--acid)}
|
||||||
|
.topnav{position:sticky;top:0;z-index:20;display:flex;gap:2px;align-items:center;flex-wrap:wrap;padding:10px 1.2rem;background:rgba(11,14,20,.82);backdrop-filter:blur(10px);border-bottom:1px solid var(--line)}
|
||||||
|
.topnav .brand{font-weight:600;color:var(--ink);text-decoration:none;margin-right:14px;font-size:15px}
|
||||||
|
.topnav .brand .g{color:var(--acid)}
|
||||||
|
.topnav a{color:var(--mut);text-decoration:none;padding:6px 12px;border-radius:8px;font-size:14px}
|
||||||
|
.topnav a:hover{color:var(--ink);background:var(--panel)}
|
||||||
|
.topnav a.here{color:var(--acid);background:rgba(214,255,61,.08)}
|
||||||
|
.topnav a.portal{margin-left:auto;background:rgba(214,255,61,.10);color:var(--acid);font-weight:500}
|
||||||
|
.wrap{max-width:900px;margin:0 auto;padding:0 1.2rem 3rem}
|
||||||
|
header.hero{text-align:center;padding:3rem 1.2rem 1rem}
|
||||||
|
.mark{font-size:44px;line-height:1}
|
||||||
|
h1{font-size:clamp(1.6rem,4.5vw,2.2rem);margin:.3rem 0}
|
||||||
|
h1 .g{color:var(--acid)}
|
||||||
|
.tag{color:var(--mut);max-width:620px;margin:0 auto}
|
||||||
|
.toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin:18px 0 8px}
|
||||||
|
input[type=text],select{padding:9px 12px;border-radius:10px;border:1px solid #ffffff22;background:#0e131b;color:var(--ink);font:inherit;font-size:14px;outline:none}
|
||||||
|
select{appearance:auto}
|
||||||
|
.status{background:var(--panel2);border:1px solid var(--line);border-radius:10px;padding:10px 14px;font-size:13px;color:var(--mut);margin-top:10px}
|
||||||
|
.status.err{border-left:3px solid var(--taken);color:var(--taken)}
|
||||||
|
.row{display:grid;grid-template-columns:minmax(0,1fr) auto auto;gap:10px 16px;align-items:center;padding:14px 16px;background:var(--panel2);border:1px solid var(--line);border-radius:12px;margin-top:8px}
|
||||||
|
.row .n{font-family:var(--mono);font-size:16px}
|
||||||
|
.row .n .tld{color:var(--acid)}
|
||||||
|
.row .price{font-family:var(--mono);text-align:right}
|
||||||
|
.row .price b{font-size:16px}
|
||||||
|
.row .price span{display:block;color:var(--dim);font-size:12px}
|
||||||
|
.row .meta{grid-column:1/-1;color:var(--dim);font-size:12px;display:flex;gap:10px;flex-wrap:wrap}
|
||||||
|
.btn{display:inline-flex;align-items:center;gap:6px;padding:9px 16px;border-radius:10px;background:#4b7bec;color:#fff;font:inherit;font-size:14px;border:none;cursor:pointer;text-decoration:none;line-height:1.2}
|
||||||
|
.btn.acid{background:var(--acid);color:#0b0e14;font-weight:600}
|
||||||
|
.btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink)}
|
||||||
|
.btn.small{padding:6px 11px;font-size:12.5px}
|
||||||
|
.btn:disabled{opacity:.45;cursor:not-allowed}
|
||||||
|
.badge{display:inline-block;font-size:11px;padding:2px 9px;border-radius:999px;font-weight:600;background:rgba(214,255,61,.14);color:var(--acid)}
|
||||||
|
.empty{padding:28px;text-align:center;color:var(--mut);border:1px dashed var(--line);border-radius:12px;margin-top:10px}
|
||||||
|
.note{background:var(--panel);border:1px solid var(--line);border-left:3px solid var(--acid);border-radius:10px;padding:14px 18px;color:var(--mut);font-size:14px;margin-top:1.6rem}
|
||||||
|
.note b{color:var(--ink)}
|
||||||
|
/* buy dialog */
|
||||||
|
.modal{position:fixed;inset:0;background:rgba(6,9,14,.85);backdrop-filter:blur(3px);display:none;align-items:flex-start;justify-content:center;padding:4vh 1rem;overflow-y:auto;z-index:50}
|
||||||
|
.modal.open{display:flex}
|
||||||
|
.sheet{background:var(--panel);border:1px solid var(--line);border-radius:16px;max-width:560px;width:100%;padding:24px 26px;position:relative}
|
||||||
|
.sheet h3{margin:0 0 .3rem;font-family:var(--mono);font-size:1.2rem}
|
||||||
|
.sheet .sub{color:var(--mut);font-size:13px;margin:0 0 1rem}
|
||||||
|
.sheet .x{position:absolute;top:14px;right:16px;background:none;border:none;color:var(--dim);font-size:22px;cursor:pointer}
|
||||||
|
.kv{display:grid;grid-template-columns:150px 1fr;gap:6px 12px;font-size:13.5px}
|
||||||
|
.kv .k{color:var(--dim)} .kv .v{font-family:var(--mono);word-break:break-all}
|
||||||
|
.steps{margin-top:12px;font-family:var(--mono);font-size:12px;color:var(--mut);background:#0e131b;border:1px solid var(--line);border-radius:8px;padding:10px 12px;max-height:150px;overflow:auto;display:none}
|
||||||
|
.steps.on{display:block}
|
||||||
|
.steps div::before{content:"› ";color:var(--acid)}
|
||||||
|
.msg{font-size:13px;margin-top:12px} .msg.err{color:var(--taken)} .msg.ok{color:var(--ok)}
|
||||||
|
.actions{display:flex;gap:10px;justify-content:flex-end;margin-top:16px}
|
||||||
|
footer{border-top:1px solid var(--line);padding:2rem 0 3rem;color:var(--dim);font-size:13px;text-align:center;margin-top:2.5rem}
|
||||||
|
@media (max-width:560px){.row{grid-template-columns:1fr auto}.row .price{grid-column:1/-1;text-align:left}}
|
||||||
|
</style>
|
||||||
|
<script src="./js/i18n.js?v=20260917h"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="topnav">
|
||||||
|
<a class="brand" href="./">⭐ <span class="g">Sirius.X</span></a>
|
||||||
|
<a href="./#try">Try it</a>
|
||||||
|
<a href="./#search-input">✏️ Register</a>
|
||||||
|
<a href="./tld.html">🌐 TLD</a>
|
||||||
|
<a href="./market.html" class="here">🛒 Market</a>
|
||||||
|
<a href="./portal.html" class="portal">🔑 Sign in</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<header class="hero">
|
||||||
|
<div class="mark">🛒</div>
|
||||||
|
<h1>Names <span class="g">for sale</span></h1>
|
||||||
|
<p class="tag">Every sale is a single Bitcoin Cash transaction. The seller signed an offer that pays them only when the
|
||||||
|
certificate moves to you — you add your coins, sign, broadcast. No escrow, no middleman, no way for either side to cheat.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="wrap">
|
||||||
|
<div class="toolbar">
|
||||||
|
<input type="text" id="q" placeholder="Filter names…" autocomplete="off" style="max-width:300px">
|
||||||
|
<select id="sort"><option value="price">Sort: price</option><option value="name">Sort: name</option><option value="new">Sort: newest</option></select>
|
||||||
|
<select id="tld"><option value="">All TLDs</option></select>
|
||||||
|
<span id="count" style="color:var(--dim);font-size:12.5px"></span>
|
||||||
|
</div>
|
||||||
|
<div class="status" id="status">Loading listings…</div>
|
||||||
|
<div id="list"></div>
|
||||||
|
|
||||||
|
<div class="note"><b>Selling a name?</b> Open it in your <a href="./portal.html">dashboard</a>, choose <b>Sell</b>, set a price. The listing is a signed offer stored by the gateway; cancelling it moves your certificate once so the old offer can never be used.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="buy">
|
||||||
|
<div class="sheet">
|
||||||
|
<button class="x" id="buy-close" title="close">×</button>
|
||||||
|
<h3 id="buy-name">—</h3>
|
||||||
|
<p class="sub">One transaction: your coins pay the seller, the certificate lands in your wallet, and the registry is told about the new owner. Records stay as they are — you can change them in the dashboard right after.</p>
|
||||||
|
<div class="kv" id="buy-kv"></div>
|
||||||
|
<div class="steps" id="buy-steps"></div>
|
||||||
|
<div class="msg" id="buy-msg"></div>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn ghost" id="buy-cancel">Cancel</button>
|
||||||
|
<a class="btn ghost" id="buy-signin" href="./portal.html" hidden>Sign in to buy →</a>
|
||||||
|
<button class="btn acid" id="buy-go" disabled>Buy — sign & broadcast</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<footer id="site-footer"></footer>
|
||||||
|
|
||||||
|
<script type="importmap">
|
||||||
|
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||||
|
</script>
|
||||||
|
<script src="./js/session.js?v=20260917dash"></script>
|
||||||
|
<script src="./js/pricing.js?v=20260917market"></script>
|
||||||
|
<script type="module" src="./js/market.js?v=20260917market"></script>
|
||||||
|
<script defer src="./js/theme.js?v=20260910font"></script>
|
||||||
|
<script defer src="./js/site-footer.js?v=20260917market"></script>
|
||||||
|
<script defer src="./js/profile-menu.js?v=20260917dash"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
43
portal.html
43
portal.html
|
|
@ -222,7 +222,7 @@
|
||||||
.addrow{grid-template-columns:1fr 1fr}
|
.addrow{grid-template-columns:1fr 1fr}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<script src="./js/i18n.js?v=20260917g"></script>
|
<script src="./js/i18n.js?v=20260917h"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -231,6 +231,7 @@
|
||||||
<a href="./#try">Try it</a>
|
<a href="./#try">Try it</a>
|
||||||
<a href="./#search-input">✏️ Register</a>
|
<a href="./#search-input">✏️ Register</a>
|
||||||
<a href="./tld.html">🌐 TLD</a>
|
<a href="./tld.html">🌐 TLD</a>
|
||||||
|
<a href="./market.html">🛒 Market</a>
|
||||||
<a href="./theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
|
<a href="./theseus/"><svg width="14" height="14" viewBox="0 0 64 64" style="vertical-align:-2px;margin-right:4px" aria-hidden="true"><rect width="64" height="64" rx="12" fill="#0b0e14"/><path d="M32 6 L38 32 L26 32 Z" fill="#e04e4e"/><path d="M38 32 L32 58 L26 32 Z" fill="#d6ff3d"/><circle cx="32" cy="32" r="3" fill="#0b0e14"/></svg>Theseus</a>
|
||||||
<a href="./portal.html" class="portal here">🔑 Sign in</a>
|
<a href="./portal.html" class="portal here">🔑 Sign in</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
@ -313,9 +314,35 @@
|
||||||
<button data-sub="content">Content & hosting</button>
|
<button data-sub="content">Content & hosting</button>
|
||||||
<button data-sub="redirect">Redirect</button>
|
<button data-sub="redirect">Redirect</button>
|
||||||
<button data-sub="transfer">Transfer</button>
|
<button data-sub="transfer">Transfer</button>
|
||||||
|
<button data-sub="sell">Sell</button>
|
||||||
<button data-sub="export">Export</button>
|
<button data-sub="export">Export</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="sub" data-sub="sell" hidden>
|
||||||
|
<div class="card" style="margin-top:0">
|
||||||
|
<h3>Sell this name</h3>
|
||||||
|
<p class="hint">You sign an offer once: "whoever pays me this price gets the certificate". The offer is stored by the gateway and shown on the <a href="./market.html" target="_blank" rel="noopener">market</a>. A buyer completes it in one transaction — you are paid in the same transaction that moves the name, or not at all. No escrow, nothing to trust.</p>
|
||||||
|
<div class="status" id="sell-status">Checking the market…</div>
|
||||||
|
<div id="sell-form">
|
||||||
|
<div class="row" style="align-items:flex-end">
|
||||||
|
<div><label style="margin-top:0">Price (USD)</label><input type="text" id="sell-usd" placeholder="25" inputmode="decimal" style="max-width:160px"></div>
|
||||||
|
<div><label style="margin-top:0">Price (sat)</label><input type="text" id="sell-sats" placeholder="6250000" inputmode="numeric" style="max-width:200px"></div>
|
||||||
|
<span class="dim" id="sell-rate"></span>
|
||||||
|
</div>
|
||||||
|
<div class="row end"><button class="btn acid" id="sell-list">List for sale →</button></div>
|
||||||
|
</div>
|
||||||
|
<div id="sell-active" class="hidden">
|
||||||
|
<div class="kv" id="sell-kv"></div>
|
||||||
|
<div class="row end">
|
||||||
|
<button class="btn danger" id="sell-cancel">Cancel listing →</button>
|
||||||
|
</div>
|
||||||
|
<p class="hint">Cancelling removes the offer from the market and moves your certificate once (a no-op update, a few cents), so the signed offer can never be completed later.</p>
|
||||||
|
</div>
|
||||||
|
<div class="steps-log" id="sell-log"></div>
|
||||||
|
<div class="msg" id="sell-msg"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="sub" data-sub="summary">
|
<div class="sub" data-sub="summary">
|
||||||
<div class="grid2">
|
<div class="grid2">
|
||||||
<div class="card" style="margin-top:0">
|
<div class="card" style="margin-top:0">
|
||||||
|
|
@ -475,6 +502,12 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card" id="td-cosign-card">
|
||||||
|
<h3>Pending approvals</h3>
|
||||||
|
<p class="hint">Registrations under this TLD that wait for your co-signature. Approving signs your TLD certificate into the buyer's transaction and broadcasts it; the certificate returns to you in the same transaction. Nothing can be registered under a co-sign or private TLD without this step.</p>
|
||||||
|
<div id="td-cosign" class="list"></div>
|
||||||
|
<div class="msg" id="td-cosign-msg"></div>
|
||||||
|
</div>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h3>Names registered under this TLD</h3>
|
<h3>Names registered under this TLD</h3>
|
||||||
<p class="hint">Everyone's registrations, from the public registry.</p>
|
<p class="hint">Everyone's registrations, from the public registry.</p>
|
||||||
|
|
@ -607,11 +640,11 @@
|
||||||
</script>
|
</script>
|
||||||
<script src="./js/session.js?v=20260917dash"></script>
|
<script src="./js/session.js?v=20260917dash"></script>
|
||||||
<script src="./js/pin-escrow.js?v=20260909pin"></script>
|
<script src="./js/pin-escrow.js?v=20260909pin"></script>
|
||||||
<script src="./js/pricing.js?v=20260916private"></script>
|
<script src="./js/pricing.js?v=20260917market"></script>
|
||||||
<script type="module" src="./js/dashboard.js?v=20260917dash2"></script>
|
<script type="module" src="./js/dashboard.js?v=20260917market"></script>
|
||||||
<script defer src="./js/theme.js?v=20260910font"></script>
|
<script defer src="./js/theme.js?v=20260910font"></script>
|
||||||
<script defer src="./js/site-footer.js?v=20260907rel"></script>
|
<script defer src="./js/site-footer.js?v=20260917market"></script>
|
||||||
<script type="module" src="./js/register-flow.js?v=20260917dash"></script>
|
<script type="module" src="./js/register-flow.js?v=20260917market"></script>
|
||||||
<script defer src="./js/profile-menu.js?v=20260917dash"></script>
|
<script defer src="./js/profile-menu.js?v=20260917dash"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@
|
||||||
.muted{color:var(--mut);font-size:13px}
|
.muted{color:var(--mut);font-size:13px}
|
||||||
code{font-family:var(--mono);background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-size:12px}
|
code{font-family:var(--mono);background:#0e131b;border:1px solid var(--line);border-radius:6px;padding:1px 6px;font-size:12px}
|
||||||
</style>
|
</style>
|
||||||
<script src="./js/i18n.js?v=20260917g"></script>
|
<script src="./js/i18n.js?v=20260917h"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="bar">
|
<div class="bar">
|
||||||
|
|
|
||||||
8
tld.html
8
tld.html
|
|
@ -115,7 +115,7 @@
|
||||||
<script type="importmap">
|
<script type="importmap">
|
||||||
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
{ "imports": { "@bitauth/libauth": "https://silentmode.st/js/libauth.js" } }
|
||||||
</script>
|
</script>
|
||||||
<script src="./js/i18n.js?v=20260917g"></script>
|
<script src="./js/i18n.js?v=20260917h"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
|
|
@ -302,12 +302,12 @@
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script type="module" src="./js/register-flow.js?v=20260917dash"></script>
|
<script type="module" src="./js/register-flow.js?v=20260917market"></script>
|
||||||
<script src="./js/pricing.js?v=20260916private"></script>
|
<script src="./js/pricing.js?v=20260917market"></script>
|
||||||
<script src="./js/session.js?v=20260917dash"></script>
|
<script src="./js/session.js?v=20260917dash"></script>
|
||||||
<script src="./js/pin-escrow.js?v=20260909pin"></script>
|
<script src="./js/pin-escrow.js?v=20260909pin"></script>
|
||||||
<script defer src="./js/theme.js?v=20260910font"></script>
|
<script defer src="./js/theme.js?v=20260910font"></script>
|
||||||
<script defer src="./js/site-footer.js?v=20260907rel"></script>
|
<script defer src="./js/site-footer.js?v=20260917market"></script>
|
||||||
<script defer src="./js/profile-menu.js?v=20260917dash"></script>
|
<script defer src="./js/profile-menu.js?v=20260917dash"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue