diff --git a/package-lock.json b/package-lock.json
index 369d0ad4..e5f3caad 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "imwald",
- "version": "23.13.3",
+ "version": "23.13.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "imwald",
- "version": "23.13.3",
+ "version": "23.13.5",
"license": "MIT",
"dependencies": {
"@asciidoctor/core": "^3.0.4",
diff --git a/package.json b/package.json
index eb69512e..03e10443 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "imwald",
- "version": "23.13.4",
+ "version": "23.13.5",
"description": "Imwald — a user-friendly Nostr client focused on relay feed browsing, publications, and relay discovery",
"private": true,
"type": "module",
diff --git a/src/components/PaytoDialog/index.tsx b/src/components/PaytoDialog/index.tsx
index a2d9982e..375d154c 100644
--- a/src/components/PaytoDialog/index.tsx
+++ b/src/components/PaytoDialog/index.tsx
@@ -14,7 +14,7 @@ import {
SelectTrigger,
SelectValue
} from '@/components/ui/select'
-import { ArrowRight, Copy, Zap } from 'lucide-react'
+import { ArrowRight, Copy, Wallet, Zap } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { closeModal } from '@getalby/bitcoin-connect-react'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
@@ -23,8 +23,12 @@ import { releaseBodyScrollLocks } from '@/lib/react-remove-scroll-body-cleanup'
import {
filterPaytoPaymentOpenHandlersForDevice,
getPaytoTypeInfo,
+ isLikelyMobileWalletUserAgent,
+ isPaytoHttpOpenUrl,
openPaytoPaymentTarget,
- resolvePaytoPaymentOpenHandlers
+ openPaytoResolvedUrl,
+ resolvePaytoPaymentOpenHandlers,
+ resolvePaytoProfileUrl
} from '@/lib/payto'
import { cn } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
@@ -99,6 +103,19 @@ export default function PaytoDialog({
[openHandlers, selectedOpenHandlerId]
)
+ const walletOpenUri = useMemo(
+ () => (isLightning ? null : resolvePaytoProfileUrl(type, authority)),
+ [isLightning, type, authority]
+ )
+
+ const showPrimaryOpen = isLikelyMobileWalletUserAgent() && !!walletOpenUri
+
+ const handleOpenWallet = () => {
+ if (!walletOpenUri) return
+ openPaytoResolvedUrl(walletOpenUri)
+ closeForWalletFlow()
+ }
+
const handleCopy = (text: string, copyLabel?: string) => {
navigator.clipboard.writeText(text)
toast.success(copyLabel ? t('Copied {{label}} address', { label: copyLabel }) : t('Copied to clipboard'))
@@ -145,7 +162,9 @@ export default function PaytoDialog({
{isLightning
? t('Create a BOLT11 invoice from this Lightning address, then pay in your connected wallet or another app.')
- : t('Payment address – copy to use in your wallet or app')}
+ : showPrimaryOpen
+ ? t('Open in your wallet app or copy the address below.')
+ : t('Payment address – copy to use in your wallet or app')}
@@ -166,23 +185,39 @@ export default function PaytoDialog({
{authority}
-
-
-
+
+ {showPrimaryOpen && walletOpenUri ? (
+
+ ) : null}
+
+
+
+
>
)}
diff --git a/src/data/payto-types.json b/src/data/payto-types.json
index 289889e6..bbe5c198 100644
--- a/src/data/payto-types.json
+++ b/src/data/payto-types.json
@@ -340,7 +340,10 @@
"logoAssetPath": "src/assets/payto_logos/bitcoin-cash-bch-logo.svg",
"walletOpen": {
"scheme": "bitcoincash",
- "walletApps": ["cakewallet", "ledger"]
+ "walletApps": ["cakewallet", "ledger"],
+ "walletAppUriTemplates": {
+ "cakewallet": "bitcoincash:{authority}"
+ }
},
"authority": {
"placeholder": "bitcoincash:… or q…",
diff --git a/src/i18n/locales/en.ts b/src/i18n/locales/en.ts
index 2037bd04..dbc63f53 100644
--- a/src/i18n/locales/en.ts
+++ b/src/i18n/locales/en.ts
@@ -126,6 +126,7 @@ export default {
"Lightning payment address – copy to pay via your wallet": "Lightning payment address – copy to pay via your wallet",
"Payment address": "Payment address",
"Payment address – copy to use in your wallet or app": "Payment address – copy to use in your wallet or app",
+ "Open in your wallet app or copy the address below.": "Open in your wallet app or copy the address below.",
"Click to open payment options": "Click to open payment options",
"Click to copy address": "Click to copy address",
"Open on website": "Open on website",
diff --git a/src/lib/payto-registry.ts b/src/lib/payto-registry.ts
index 1ae25e4b..a480d807 100644
--- a/src/lib/payto-registry.ts
+++ b/src/lib/payto-registry.ts
@@ -33,6 +33,11 @@ export type PaytoWalletOpenRow = {
requireAtSign?: boolean
requirePrefix?: string
walletApps?: string[]
+ /**
+ * Per-app URI template overrides (e.g. Cake Wallet needs native `bitcoincash:` for BCH
+ * because its wallet label is "bitcoin cash", not `bitcoincash`).
+ */
+ walletAppUriTemplates?: Record
/** When true, {@link walletApps} are hidden until PaytoDialog supplies a BOLT11 (see `_openWith.bolt11Invoice`). */
deferWalletAppsUntilBolt11?: boolean
}
diff --git a/src/lib/payto-targets.test.ts b/src/lib/payto-targets.test.ts
index 66289a27..6dedd9f4 100644
--- a/src/lib/payto-targets.test.ts
+++ b/src/lib/payto-targets.test.ts
@@ -247,6 +247,14 @@ describe('resolvePaytoPaymentOpenHandlers', () => {
).toEqual(['PayPal'])
})
+ it('uses native bitcoincash: URI for Cake Wallet on BCH (not cakewallet:bitcoincash)', () => {
+ const addr = 'qzrvw7kxr6a2vwm6hjcpaym8znf8t4nlyd8y4f2f8k'
+ const handlers = resolvePaytoPaymentOpenHandlers('bitcoin-cash', addr)
+ const cake = handlers.find((h) => h.openTargetName === 'Cake Wallet')
+ expect(cake?.href).toBe(`bitcoincash:${addr}`)
+ expect(cake?.href).not.toMatch(/^cakewallet:/)
+ })
+
it('never mixes fiat web links or lightning wallets across payto types', () => {
const monero = resolvePaytoPaymentOpenHandlers(
'monero',
diff --git a/src/lib/payto-targets.ts b/src/lib/payto-targets.ts
index 1ba44b0e..926785c6 100644
--- a/src/lib/payto-targets.ts
+++ b/src/lib/payto-targets.ts
@@ -155,12 +155,13 @@ export function resolveWalletAppHref(
const build = WALLET_APP_BUILDERS[app.builder]
if (build) return build(coinScheme, auth)
}
- if (!app.uriTemplate) return null
+ const template = walletOpen?.walletAppUriTemplates?.[appId] ?? app.uriTemplate
+ if (!template) return null
const payload =
coinScheme === 'lightning' || coinScheme === 'bolt12'
? auth.replace(/^lightning:/i, '')
: auth
- return substituteAuthority(app.uriTemplate.replace(/\{coinScheme\}/g, coinScheme), payload)
+ return substituteAuthority(template.replace(/\{coinScheme\}/g, coinScheme), payload)
}
/** Primary open URL: native wallet URI, then profile web template. */
@@ -314,11 +315,16 @@ export function filterPaytoPaymentOpenHandlersForDevice(
return handlers.filter((h) => !h.mobileOnly)
}
-/** Open a resolved handler (new tab for https, assign for wallet schemes). */
-export function openPaytoPaymentTarget(handler: PaytoPaymentOpenHandler): void {
- if (handler.isHttp) {
- window.open(handler.href, '_blank', 'noopener,noreferrer')
+/** Open a resolved payto / wallet URL (https in a new tab, coin schemes via assign). */
+export function openPaytoResolvedUrl(url: string): void {
+ if (isPaytoHttpOpenUrl(url)) {
+ window.open(url, '_blank', 'noopener,noreferrer')
return
}
- window.location.assign(handler.href)
+ window.location.assign(url)
+}
+
+/** Open a resolved handler (new tab for https, assign for wallet schemes). */
+export function openPaytoPaymentTarget(handler: PaytoPaymentOpenHandler): void {
+ openPaytoResolvedUrl(handler.href)
}
diff --git a/src/lib/payto.ts b/src/lib/payto.ts
index 9ba86f5e..83699dfa 100644
--- a/src/lib/payto.ts
+++ b/src/lib/payto.ts
@@ -29,6 +29,7 @@ export {
export {
filterPaytoPaymentOpenHandlersForDevice,
openPaytoPaymentTarget,
+ openPaytoResolvedUrl,
resolvePaytoPaymentOpenHandlers,
resolvePaytoProfileUrl,
isPaytoHttpOpenUrl,