From 044030c0aabca3a711fbd882c3d24d793e8af919 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Tue, 9 Sep 2025 22:30:12 -0700 Subject: homepage update --- src/locales/en.json | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'src/locales') diff --git a/src/locales/en.json b/src/locales/en.json index 75160116..b0d5154f 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -102,23 +102,19 @@ "header": "FAQ", "cost": { "question": "What does it cost?", - "answer": "Receiving payments into your account is free. Withdrawals to external wallets incur a small fee that varies depending on network conditions." + "answer": "Receiving payments is free. Sending to other Coinos accounts is free. Withdrawals to external wallets are free if you use the same network you received on, otherwise there's a 0.4% fee for Bitcoin or 0.1% for Lightning or Liquid." }, "compatibility": { "question": "Do I need any special device or software?", - "answer": "No, Coinos works anywhere that has Internet. Merchants might use it on a tablet or put up a QR code for customers to scan with their phone." - }, - "safety": { - "question": "When do my funds settle?", - "answer": "Lightning payments are settled instantly. You can withdraw your balance to another wallet within seconds. On-chain deposits take 5-10 minutes to confirm on average." + "answer": "Coinos works on any device that has Internet and a web browser. You can receive payment notifications within the app, via email or push notification. We do have PoS terminals and receipt printers available for those who prefer physical notifications." }, "pos": { "question": "Can Coinos integrate with my existing PoS?", - "answer": "We recommend entering sales into your existing PoS as cash transactions." + "answer": "We can log sales into Square. If you're using something else, you can record Bitcoin sales as \"cash\"." }, "bitcoin": { "question": "What can I do with Bitcoin?", - "answer": "Spend it at other merchants or pay your expenses with it by getting your staff, suppliers and landlord to take it." + "answer": "Spend it at other merchants, convince your staff, landlord or suppliers to let you pay your expenses with it, or hold it as an investment." } }, "footer": { @@ -145,7 +141,7 @@ }, "step3": { "header": "3. That's all!", - "description": "Spend your coin immediately or watch it grow in value." + "description": "Easy, right? Tell us when you're ready for us to promote your business to the Bitcoin community!" } }, "invoice": { -- cgit v1.2.3 From d2b71e6eb18b34299f199675f05f63f87f3a9208 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 10 Sep 2025 10:39:31 -0700 Subject: update faq --- src/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/locales') diff --git a/src/locales/en.json b/src/locales/en.json index b0d5154f..dd6e29f1 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -110,7 +110,7 @@ }, "pos": { "question": "Can Coinos integrate with my existing PoS?", - "answer": "We can log sales into Square. If you're using something else, you can record Bitcoin sales as \"cash\"." + "answer": "We can log sales into Square automatically. On other systems you might choose to record Bitcoin sales under \"cash\"." }, "bitcoin": { "question": "What can I do with Bitcoin?", -- cgit v1.2.3 From 78667b3f97a1e6c4bd7d09b7ff69602e2e771590 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 6 Nov 2025 16:47:21 -0500 Subject: Add translatability to new DM-related text --- src/locales/en.json | 12 ++++++++++++ src/routes/(app)/dm/[recipient]/+page.svelte | 21 +++++++++------------ src/routes/(app)/settings/nostr/+page.svelte | 4 ++-- 3 files changed, 23 insertions(+), 14 deletions(-) (limited to 'src/locales') diff --git a/src/locales/en.json b/src/locales/en.json index dd6e29f1..a8b8a06a 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -92,6 +92,16 @@ "description": "Over 500 businesses worldwide are accepting bitcoin with Coinos. We hope that yours will be next!" } }, + "dm": { + "header": "Direct Messages", + "recipientMsg": "You are sending nostr messages to [R].", + "sendMessage": "Send Message", + "expiry": "Enable message expiry", + "days": "days", + "relaysNotFound": "Could not find [R]’s DM relays.", + "noRelaysSet": "WARNING: You haven't set any preferred relays. You won't be able to receive any messages or see the messages you've sent.", + "nostrSettingsLink": "Go to your Nostr settings to set your relays." + }, "error": { "header": "Error", "message": "Please try again or contact support.", @@ -337,6 +347,8 @@ "revealNsec": "Reveal secret key", "hideSeed": "Hide seed phrase", "revealSeed": "Reveal seed phrase", + "preferredDM": "Preferred DM Relays", + "preferredDMDescription": "Direct Messages meant for you will be published to these relays. To get DMs on multiple relays, put each relay URL on its own line.", "saveSettings": "Save settings", "bannerImage": "Banner Image", "profileImage": "Profile Image", diff --git a/src/routes/(app)/dm/[recipient]/+page.svelte b/src/routes/(app)/dm/[recipient]/+page.svelte index 35b5221e..26b1907d 100644 --- a/src/routes/(app)/dm/[recipient]/+page.svelte +++ b/src/routes/(app)/dm/[recipient]/+page.svelte @@ -5,6 +5,7 @@ import { SimplePool } from 'nostr-tools/pool'; import * as libnip17 from '$lib/nip17'; import { sign, getPrivateKey } from '$lib/nostr'; + import { t } from "$lib/translations"; let { data } = $props(); const { user, recipient } = data; @@ -22,6 +23,7 @@ let relayWarningShown = $state(false); let expiryEnabled = $state(false); let expiryDays = $state(7); + let canSend = $state(false); const appendMultimap = (map: Map, key: string, value: object) => { if (map.has(key)) { @@ -104,13 +106,8 @@ relayWarningShown = false; loadEvents(relays); }); - getPreferredRelays(recipient.pubkey).then((relays) => { - let sendButton = document.getElementById("send-message"); - if (!relays || relays.length == 0) { - sendButton.disabled = true; - sendButton.value = `Could not find ${recipient.username}'s DM relays.`; - } - }); + getPreferredRelays(recipient.pubkey) + .then((relays) => canSend = relays && relays.length > 0); const sendMessage = async (message: string) => { const expiry = expiryEnabled ? expiryDays : null; @@ -194,8 +191,8 @@

Direct Messages

-

You are sending nostr messages to {recipient.username}.

+ >{$t("dm.header")} +

{$t("dm.recipientMsg").replace("[R]", recipient.username)}

{#each dates as day}

[{new Date(day * 86400 * 1000).toDateString()}]

@@ -210,10 +207,10 @@ - Enable message expiry in days. - sendMessage(text)}> + {$t("dm.expiry")}: {$t("dm.days")}. + sendMessage(text)}> {#if relayWarningShown} -

WARNING: You haven't set any preferred relays. You won't be able to receive any messages or see the messages you've sent. Go to your Nostr settings to set your relays.

+

{$t("dm.noRelaysSet")} {$t("dm.nostrSettingsLink")}

{/if}
diff --git a/src/routes/(app)/settings/nostr/+page.svelte b/src/routes/(app)/settings/nostr/+page.svelte index fee9885d..8557d522 100644 --- a/src/routes/(app)/settings/nostr/+page.svelte +++ b/src/routes/(app)/settings/nostr/+page.svelte @@ -259,10 +259,10 @@ {/if} - Preferred DM Relays + {$t("user.settings.preferredDM")}

- Direct Messages meant for you will be published to these relays. To get DMs on multiple relays, put each relay URL on its own line. + {$t("user.settings.preferredDMDescription")}

-- cgit v1.2.3 From 1e4d0c3d774b8d135049c7d429e2b6bc91800330 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 6 Nov 2025 16:47:46 -0500 Subject: Add French translation for new strings --- src/locales/fr.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/locales') diff --git a/src/locales/fr.json b/src/locales/fr.json index 7ae6896b..76d3c5c2 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -24,6 +24,16 @@ } } }, + "dm": { + "header": "Messages directs", + "recipientMsg": "Vous envoyez messages nostr à [R].", + "sendMessage": "Envoyez Message", + "expiry": "Expirez messages", + "days": "jours", + "relaysNotFound": "Ne pouvait pas trouvez les relais MD de [R].", + "noRelaysSet": "AVERTISSEMENT: Vous n’avez pas specifié vos relais préferés pour messages directs. Vous ne pouvez pas reçevoir des messages ou voir les messages que vous avez envoyé.", + "nostrSettingsLink": "Spécifiez vos relais en vos préférences Nostr." + }, "error": { "header": "Erreur", "message": "S'il vous plait réessayez ou contactez l'assistance.", @@ -236,6 +246,8 @@ "hours": "heures", "hideSeed": "Cacher la phrase mnémotechnique", "revealSeed": "Montrer la phrase mnémotechnique", + "preferredDM": "Relais MD Préferés", + "preferredDMDescription": "Messages directs destinés à vous seront publiés à ces relais. Pour obtiendre des messages sur plusieurs relais, met chaque URL sur son propre ligne.", "notifications": "Notifications de paiement", "notificationsDesc": "Reçevez un email quand un paiement arrive à votre compte", "seedPhrase": "Phrase mnémotechnique", -- cgit v1.2.3 From 05246ae8d0597ef86f912476ed0a369424d19a72 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Fri, 7 Nov 2025 13:47:47 -0500 Subject: Dim messages near expiry --- src/locales/en.json | 1 + src/locales/fr.json | 1 + src/routes/(app)/dm/[recipient]/+page.svelte | 20 ++++++++++++++++---- 3 files changed, 18 insertions(+), 4 deletions(-) (limited to 'src/locales') diff --git a/src/locales/en.json b/src/locales/en.json index a8b8a06a..427c8d96 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -97,6 +97,7 @@ "recipientMsg": "You are sending nostr messages to [R].", "sendMessage": "Send Message", "expiry": "Enable message expiry", + "fadedWarning": "To avoid metadata leaks, messages expire at a random time up to 2 days after you specify; each message has its own random expiry time. Messages which appear faded are close to expiring; nearby messages may be missing.", "days": "days", "relaysNotFound": "Could not find [R]’s DM relays.", "noRelaysSet": "WARNING: You haven't set any preferred relays. You won't be able to receive any messages or see the messages you've sent.", diff --git a/src/locales/fr.json b/src/locales/fr.json index 76d3c5c2..4a096420 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -29,6 +29,7 @@ "recipientMsg": "Vous envoyez messages nostr à [R].", "sendMessage": "Envoyez Message", "expiry": "Expirez messages", + "fadedWarning": "Pour protéger vos métadonées, vos messages expirent à un temps randomée au maximum 2 jours après la date que vous spécifiez; chaque message a son propre temps d’expiration randomée. Les messages qui semble faibles sont proche à expirer; les autres messages autours pouvait avoir déjà disparu.", "days": "jours", "relaysNotFound": "Ne pouvait pas trouvez les relais MD de [R].", "noRelaysSet": "AVERTISSEMENT: Vous n’avez pas specifié vos relais préferés pour messages directs. Vous ne pouvez pas reçevoir des messages ou voir les messages que vous avez envoyé.", diff --git a/src/routes/(app)/dm/[recipient]/+page.svelte b/src/routes/(app)/dm/[recipient]/+page.svelte index c64df7f7..443119fe 100644 --- a/src/routes/(app)/dm/[recipient]/+page.svelte +++ b/src/routes/(app)/dm/[recipient]/+page.svelte @@ -75,6 +75,10 @@ return false; } + const expirationClose = (expiration: number) => { + return expiration <= (Date.now() / 1000 + 2 * 86400); + } + const loadEvents = async (relays: string[]) => { const wrapped = await pool.querySync( relays, { kinds: [1059], "#p": [user.pubkey], limit: DM_FETCH_LIMIT } @@ -86,6 +90,11 @@ if (!expired(event)) { const rumour = await decryptMessage(event); const created = Math.floor(rumour.created_at / 86400); + for (const tag of event.tags) { + if (tag[0] == "expiration") { + rumour.expiration = parseInt(tag[1]); + } + } appendMultimap(decryptedRumours, created, rumour); } else console.log("relay contains expired event", event.id); } @@ -130,9 +139,6 @@ message, sk, recipient.pubkey, user.pubkey, expiry); } - console.log(Date.now() / 1000); - console.log(event2.tags); - const recipientRelays = await getPreferredRelays(recipient.pubkey); const p1 = Promise.any(pool.publish(recipientRelays, event1)); const senderRelays = await getPreferredRelays(user.pubkey); @@ -166,6 +172,10 @@ color: #ff7f00; } + .expiring { + color: #7f7f7f; + } + .link { text-decoration-line: underline; } @@ -205,7 +215,7 @@
    {#each messageRumours.get(day) as rumour} {#if rumour.pubkey === recipient.pubkey || rumour.pubkey === user.pubkey} -
  • {timeString(new Date(rumour.created_at * 1000))} {dmName(rumour.pubkey)}: {rumour.content}
  • +
  • {timeString(new Date(rumour.created_at * 1000))} {dmName(rumour.pubkey)}: {rumour.content}
  • {/if} {/each}
@@ -215,6 +225,8 @@ {$t("dm.expiry")}: {$t("dm.days")}. +

{$t("dm.fadedWarning")}

+ sendMessage(text)}> {#if relayWarningShown}

{$t("dm.noRelaysSet")} {$t("dm.nostrSettingsLink")}

-- cgit v1.2.3 From 64f200944f8016725016dfaa0e6db6a6f102b776 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Thu, 13 Nov 2025 17:24:48 -0500 Subject: Only send expiring DMs to compatible relays --- src/locales/en.json | 1 + src/locales/fr.json | 1 + src/routes/(app)/dm/[recipient]/+page.svelte | 40 ++++++++++++++++++++++++---- 3 files changed, 37 insertions(+), 5 deletions(-) (limited to 'src/locales') diff --git a/src/locales/en.json b/src/locales/en.json index 427c8d96..869a6beb 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -97,6 +97,7 @@ "recipientMsg": "You are sending nostr messages to [R].", "sendMessage": "Send Message", "expiry": "Enable message expiry", + "cantSendExpiring": "None of [R]'s DM relays support message expiry. You can't send expiring messages to them.", "fadedWarning": "To avoid metadata leaks, messages expire at a random time up to 2 days after you specify; each message has its own random expiry time. Messages which appear faded are close to expiring; nearby messages may be missing.", "days": "days", "relaysNotFound": "Could not find [R]’s DM relays.", diff --git a/src/locales/fr.json b/src/locales/fr.json index 4a096420..d8533bd7 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -29,6 +29,7 @@ "recipientMsg": "Vous envoyez messages nostr à [R].", "sendMessage": "Envoyez Message", "expiry": "Expirez messages", + "cantSendExpiring": "Aucun des relais de [R] expirent les messages expirants. Vous ne pouvez pas leur envoyer des messages expirants.", "fadedWarning": "Pour protéger vos métadonées, vos messages expirent à un temps randomée au maximum 2 jours après la date que vous spécifiez; chaque message a son propre temps d’expiration randomée. Les messages qui semble faibles sont proche à expirer; les autres messages autours pouvait avoir déjà disparu.", "days": "jours", "relaysNotFound": "Ne pouvait pas trouvez les relais MD de [R].", diff --git a/src/routes/(app)/dm/[recipient]/+page.svelte b/src/routes/(app)/dm/[recipient]/+page.svelte index 443119fe..17a8ad23 100644 --- a/src/routes/(app)/dm/[recipient]/+page.svelte +++ b/src/routes/(app)/dm/[recipient]/+page.svelte @@ -2,6 +2,7 @@ import { tick } from "svelte"; import Icon from "$comp/Icon.svelte"; import { hexToBytes, bytesToHex } from '@noble/hashes/utils'; + import { fetchRelayInformation } from 'nostr-tools/nip11'; import * as toolsnip17 from 'nostr-tools/nip17'; import { SimplePool } from 'nostr-tools/pool'; import * as libnip17 from '$lib/nip17'; @@ -25,6 +26,7 @@ let expiryEnabled = $state(false); let expiryDays = $state(7); let canSend = $state(false); + let canSendExpiring = $state(false); const appendMultimap = (map: Map, key: string, value: object) => { if (map.has(key)) { @@ -66,6 +68,20 @@ return relays; } + // Accepts a list of relays, and returns a list of the relays that support all the provided NIPs. + const relaysSupporting = async (relays: string[], nips: number[]) => { + let supportingRelays = []; + + for (const relay of relays) { + const relayInfo = await fetchRelayInformation(relay); + if (nips.every(relay => relayInfo.supported_nips.includes(relay))) { + supportingRelays.push(relay); + } + } + + return supportingRelays; + } + const expired = (event: object) => { for (const tag of event.tags) { if (tag[0] == "expiration") { @@ -120,8 +136,11 @@ relayWarningShown = false; loadEvents(relays); }); - getPreferredRelays(recipient.pubkey) - .then((relays) => canSend = relays && relays.length > 0); + getPreferredRelays(recipient.pubkey).then((relays) => { + canSend = relays && relays.length > 0; + relaysSupporting(relays, [40]) + .then((supporting) => canSendExpiring = supporting.length > 0); + }); const sendMessage = async (message: string) => { const expiry = expiryEnabled ? expiryDays : null; @@ -139,10 +158,18 @@ message, sk, recipient.pubkey, user.pubkey, expiry); } - const recipientRelays = await getPreferredRelays(recipient.pubkey); + let recipientRelays = await getPreferredRelays(recipient.pubkey); + if (expiryEnabled) { + recipientRelays = relaysSupporting(recipientRelays, [40]); + } const p1 = Promise.any(pool.publish(recipientRelays, event1)); - const senderRelays = await getPreferredRelays(user.pubkey); + + let senderRelays = await getPreferredRelays(user.pubkey); + if (expiryEnabled) { + senderRelays = relaysSupporting(senderRelays, [40]); + } const p2 = Promise.any(pool.publish(senderRelays, event2)); + await Promise.all([p1, p2]); getPreferredRelays(user.pubkey).then(loadEvents); } @@ -224,8 +251,11 @@ - {$t("dm.expiry")}: {$t("dm.days")}. + {$t("dm.expiry")}: {$t("dm.days")}.

{$t("dm.fadedWarning")}

+ {#if canSend && !canSendExpiring} +

{$t("dm.cantSendExpiring").replace("[R]", recipient.username)}

+ {/if} sendMessage(text)}> {#if relayWarningShown} -- cgit v1.2.3 From 082429c6ebe4cf78d58e9c0249bb8e83271fc778 Mon Sep 17 00:00:00 2001 From: Adrien Hopkins Date: Tue, 18 Nov 2025 11:23:39 -0500 Subject: Only put valid URLs in kind-10050 relay tags --- src/locales/en.json | 1 + src/locales/fr.json | 1 + src/routes/(app)/settings/+layout.svelte | 23 +++++++++++++++++++---- 3 files changed, 21 insertions(+), 4 deletions(-) (limited to 'src/locales') diff --git a/src/locales/en.json b/src/locales/en.json index 869a6beb..03579132 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -351,6 +351,7 @@ "revealSeed": "Reveal seed phrase", "preferredDM": "Preferred DM Relays", "preferredDMDescription": "Direct Messages meant for you will be published to these relays. To get DMs on multiple relays, put each relay URL on its own line.", + "noDMURLsWarning": "You haven't set any valid relay URLs. You will not be able to use DMs.", "saveSettings": "Save settings", "bannerImage": "Banner Image", "profileImage": "Profile Image", diff --git a/src/locales/fr.json b/src/locales/fr.json index d8533bd7..c891293d 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -250,6 +250,7 @@ "revealSeed": "Montrer la phrase mnémotechnique", "preferredDM": "Relais MD Préferés", "preferredDMDescription": "Messages directs destinés à vous seront publiés à ces relais. Pour obtiendre des messages sur plusieurs relais, met chaque URL sur son propre ligne.", + "noDMURLsWarning": "Vous n'avez pas specifié aucun URL valide. Vous ne pourrez pas utiliser les messages directs.", "notifications": "Notifications de paiement", "notificationsDesc": "Reçevez un email quand un paiement arrive à votre compte", "seedPhrase": "Phrase mnémotechnique", diff --git a/src/routes/(app)/settings/+layout.svelte b/src/routes/(app)/settings/+layout.svelte index ef4b8abf..24c54a97 100644 --- a/src/routes/(app)/settings/+layout.svelte +++ b/src/routes/(app)/settings/+layout.svelte @@ -1,4 +1,4 @@ -