Browse Source

feat: rizful

imwald
codytseng 6 months ago
parent
commit
6357fd5b44
  1. 85
      package-lock.json
  2. 2
      package.json
  3. 3
      src/PageManager.tsx
  4. 31
      src/components/CreateWalletGuideToast/index.tsx
  5. 2
      src/components/ui/button.tsx
  6. 1
      src/constants.ts
  7. 23
      src/i18n/locales/ar.ts
  8. 24
      src/i18n/locales/de.ts
  9. 23
      src/i18n/locales/en.ts
  10. 23
      src/i18n/locales/es.ts
  11. 23
      src/i18n/locales/fa.ts
  12. 24
      src/i18n/locales/fr.ts
  13. 23
      src/i18n/locales/hi.ts
  14. 23
      src/i18n/locales/it.ts
  15. 23
      src/i18n/locales/ja.ts
  16. 23
      src/i18n/locales/ko.ts
  17. 23
      src/i18n/locales/pl.ts
  18. 23
      src/i18n/locales/pt-BR.ts
  19. 23
      src/i18n/locales/pt-PT.ts
  20. 23
      src/i18n/locales/ru.ts
  21. 23
      src/i18n/locales/th.ts
  22. 23
      src/i18n/locales/zh.ts
  23. 1
      src/lib/link.ts
  24. 35
      src/pages/secondary/ProfileEditorPage/index.tsx
  25. 203
      src/pages/secondary/RizfulPage/index.tsx
  26. 32
      src/pages/secondary/SettingsPage/index.tsx
  27. 19
      src/pages/secondary/WalletPage/LightningAddressInput.tsx
  28. 75
      src/pages/secondary/WalletPage/index.tsx
  29. 34
      src/providers/ZapProvider.tsx
  30. 4
      src/routes.tsx
  31. 15
      src/services/lightning.service.ts
  32. 23
      src/services/local-storage.service.ts

85
package-lock.json generated

@ -13,7 +13,7 @@
"@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@getalby/bitcoin-connect-react": "^3.7.0", "@getalby/bitcoin-connect-react": "^3.10.0",
"@noble/hashes": "^1.6.1", "@noble/hashes": "^1.6.1",
"@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-avatar": "^1.1.2",
@ -2253,32 +2253,35 @@
"integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==" "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig=="
}, },
"node_modules/@getalby/bitcoin-connect": { "node_modules/@getalby/bitcoin-connect": {
"version": "3.7.0", "version": "3.10.0",
"resolved": "https://registry.npmjs.org/@getalby/bitcoin-connect/-/bitcoin-connect-3.7.0.tgz", "resolved": "https://registry.npmjs.org/@getalby/bitcoin-connect/-/bitcoin-connect-3.10.0.tgz",
"integrity": "sha512-9Tzn7tCJ2awniiunRvTcEQRJQEhw5hZLVBCmhckgAP0GRj5kESnoWfA1jX0WKZZVtSq/2qOfX1wMiz73gCd8gQ==", "integrity": "sha512-6UXeu0SzEmw4Fhyw9jP6PMH7dqHYQTdmdziUPxv4HE8xMO6Q+g5xPbPrK9R36ASB7P680JCQRnoJiltgP3fwLA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@getalby/lightning-tools": "^5.1.2", "@getalby/lightning-tools": "^5.2.1",
"@getalby/sdk": "^4.1.1", "@getalby/sdk": "^5.1.2",
"@lightninglabs/lnc-web": "^0.3.2-alpha", "@lightninglabs/lnc-web": "^0.3.4-alpha",
"qrcode-generator": "^1.4.4", "qrcode-generator": "1.4.4",
"zustand": "^4.5.6" "zustand": "^4.5.7"
} }
}, },
"node_modules/@getalby/bitcoin-connect-react": { "node_modules/@getalby/bitcoin-connect-react": {
"version": "3.7.0", "version": "3.10.0",
"resolved": "https://registry.npmjs.org/@getalby/bitcoin-connect-react/-/bitcoin-connect-react-3.7.0.tgz", "resolved": "https://registry.npmjs.org/@getalby/bitcoin-connect-react/-/bitcoin-connect-react-3.10.0.tgz",
"integrity": "sha512-wO8RhUlxJ4ub6vl8x8BScUaG4Z/tnLcDvJd9V4V7AOlrmrItMJfViZmc14c/WVU/RREeE3MSY2GZ0wYoH2TzxA==", "integrity": "sha512-yruMhqbzxOuNqOM/kiNpIxjtZqFHsBAl0pI4GCRlKflHrGSGlu9/vpYT1XkWWag2IDy9VnDYPvVvznAX17chjQ==",
"license": "MIT",
"dependencies": { "dependencies": {
"@getalby/bitcoin-connect": "^3.7.0" "@getalby/bitcoin-connect": "^3.10.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": "^18.2.0" "react": "^18.2.0"
} }
}, },
"node_modules/@getalby/lightning-tools": { "node_modules/@getalby/lightning-tools": {
"version": "5.1.2", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/@getalby/lightning-tools/-/lightning-tools-5.1.2.tgz", "resolved": "https://registry.npmjs.org/@getalby/lightning-tools/-/lightning-tools-5.2.1.tgz",
"integrity": "sha512-BwGm8eGbPh59BVa1gI5yJMantBl/Fdps6X4p1ZACnmxz9vDINX8/3aFoOnDlF7yyA2boXWCsReVQSr26Q2yjiQ==", "integrity": "sha512-dxOmJLJAh6qJ8rsbA5/Bwj7MSI9X3RkxxqmCedl5rfP+yKwNSdfu8i4EiCZN/tk2hNBJb8GHSCcPRNZfwfmEHg==",
"license": "MIT",
"engines": { "engines": {
"node": ">=14" "node": ">=14"
}, },
@ -2288,11 +2291,13 @@
} }
}, },
"node_modules/@getalby/sdk": { "node_modules/@getalby/sdk": {
"version": "4.1.1", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/@getalby/sdk/-/sdk-4.1.1.tgz", "resolved": "https://registry.npmjs.org/@getalby/sdk/-/sdk-5.1.2.tgz",
"integrity": "sha512-Tm0Puqm3qpXxSfUhiO8W7Uaq9Fx/Ww2aOv3sjRYL1jukLi+GRj4s65QCsjCIWaKWUN+RCJMlW3LtKFnUQC/O3A==", "integrity": "sha512-yUF9LhuvdIFOwjV1aG0ryzfwDiGBFk/CRLkRvrrM9dsE38SUjKsf1FDga5jxsKMu80nWcPZR9TiGGASWedoYPA==",
"license": "MIT",
"dependencies": { "dependencies": {
"nostr-tools": "2.9.4" "@getalby/lightning-tools": "^5.2.0",
"nostr-tools": "2.15.0"
}, },
"engines": { "engines": {
"node": ">=14" "node": ">=14"
@ -2306,6 +2311,7 @@
"version": "1.3.1", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz",
"integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==",
"license": "MIT",
"engines": { "engines": {
"node": ">= 16" "node": ">= 16"
}, },
@ -2314,19 +2320,18 @@
} }
}, },
"node_modules/@getalby/sdk/node_modules/nostr-tools": { "node_modules/@getalby/sdk/node_modules/nostr-tools": {
"version": "2.9.4", "version": "2.15.0",
"resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.9.4.tgz", "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.15.0.tgz",
"integrity": "sha512-Powumwkp+EWbdK1T8IsEX4daTLQhtWJvitfZ6OP2BdU1jJZvNlUp3SQB541UYw4uc9jgLbxZW6EZSdZoSfIygQ==", "integrity": "sha512-Jj/+UFbu3JbTAWP4ipPFNuyD4W5eVRBNAP+kmnoRCYp3bLmTrlQ0Qhs5O1xSQJTFpjdZqoS0zZOUKdxUdjc+pw==",
"license": "Unlicense",
"dependencies": { "dependencies": {
"@noble/ciphers": "^0.5.1", "@noble/ciphers": "^0.5.1",
"@noble/curves": "1.2.0", "@noble/curves": "1.2.0",
"@noble/hashes": "1.3.1", "@noble/hashes": "1.3.1",
"@scure/base": "1.1.1", "@scure/base": "1.1.1",
"@scure/bip32": "1.3.1", "@scure/bip32": "1.3.1",
"@scure/bip39": "1.2.1" "@scure/bip39": "1.2.1",
}, "nostr-wasm": "0.1.0"
"optionalDependencies": {
"nostr-wasm": "v0.1.0"
}, },
"peerDependencies": { "peerDependencies": {
"typescript": ">=5.0.0" "typescript": ">=5.0.0"
@ -2468,16 +2473,18 @@
} }
}, },
"node_modules/@lightninglabs/lnc-core": { "node_modules/@lightninglabs/lnc-core": {
"version": "0.3.2-alpha", "version": "0.3.4-alpha",
"resolved": "https://registry.npmjs.org/@lightninglabs/lnc-core/-/lnc-core-0.3.2-alpha.tgz", "resolved": "https://registry.npmjs.org/@lightninglabs/lnc-core/-/lnc-core-0.3.4-alpha.tgz",
"integrity": "sha512-H6tG+X9txCIdxTR+GPsbImzP2Juo+6Uvq/Ipaijd7xPISzgEU4J4GNE5PEHuIZqbnBo1RmpuXnFG6dmsl3PTzQ==" "integrity": "sha512-S/L1gNHqF8jW3DVXBvzVX8zyJO4O2FRfKFNE5U3rtRBaczX+fSVpK3yz/CdgBqhBzyZ+1un6nVZE/tftnfjQwA==",
"license": "MIT"
}, },
"node_modules/@lightninglabs/lnc-web": { "node_modules/@lightninglabs/lnc-web": {
"version": "0.3.2-alpha", "version": "0.3.4-alpha",
"resolved": "https://registry.npmjs.org/@lightninglabs/lnc-web/-/lnc-web-0.3.2-alpha.tgz", "resolved": "https://registry.npmjs.org/@lightninglabs/lnc-web/-/lnc-web-0.3.4-alpha.tgz",
"integrity": "sha512-3aCBugBf0NzczpJqmHn03Oq2Ju9W5n0+nOdAe+Y/Zhf6YLXdqG1PTJ2J+7TXncpiogfPYDCw95tVQqSi4Zi/ZA==", "integrity": "sha512-ARTsCm71ewJ3sWaW4DEauEXr9wG4qPpCMWGGVjbjvvo5Jvd3svLO610JLYoV7LvQhyW6dKLlAooLxYws2y9FLA==",
"license": "MIT",
"dependencies": { "dependencies": {
"@lightninglabs/lnc-core": "0.3.2-alpha", "@lightninglabs/lnc-core": "0.3.4-alpha",
"crypto-js": "4.2.0" "crypto-js": "4.2.0"
} }
}, },
@ -6756,7 +6763,8 @@
"node_modules/crypto-js": { "node_modules/crypto-js": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz",
"integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==",
"license": "MIT"
}, },
"node_modules/crypto-random-string": { "node_modules/crypto-random-string": {
"version": "2.0.0", "version": "2.0.0",
@ -13059,9 +13067,10 @@
} }
}, },
"node_modules/zustand": { "node_modules/zustand": {
"version": "4.5.6", "version": "4.5.7",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.6.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz",
"integrity": "sha512-ibr/n1hBzLLj5Y+yUcU7dYw8p6WnIVzdJbnX+1YpaScvZVF2ziugqHs+LAmHw4lWO9c/zRj+K1ncgWDQuthEdQ==", "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==",
"license": "MIT",
"dependencies": { "dependencies": {
"use-sync-external-store": "^1.2.2" "use-sync-external-store": "^1.2.2"
}, },

2
package.json

@ -23,7 +23,7 @@
"@dnd-kit/modifiers": "^9.0.0", "@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2", "@dnd-kit/utilities": "^3.2.2",
"@getalby/bitcoin-connect-react": "^3.7.0", "@getalby/bitcoin-connect-react": "^3.10.0",
"@noble/hashes": "^1.6.1", "@noble/hashes": "^1.6.1",
"@radix-ui/react-alert-dialog": "^1.1.4", "@radix-ui/react-alert-dialog": "^1.1.4",
"@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-avatar": "^1.1.2",

3
src/PageManager.tsx

@ -28,6 +28,7 @@ import { NotificationProvider } from './providers/NotificationProvider'
import { useScreenSize } from './providers/ScreenSizeProvider' import { useScreenSize } from './providers/ScreenSizeProvider'
import { routes } from './routes' import { routes } from './routes'
import modalManager from './services/modal-manager.service' import modalManager from './services/modal-manager.service'
import CreateWalletGuideToast from './components/CreateWalletGuideToast'
export type TPrimaryPageName = keyof typeof PRIMARY_PAGE_MAP export type TPrimaryPageName = keyof typeof PRIMARY_PAGE_MAP
@ -321,6 +322,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
))} ))}
<BottomNavigationBar /> <BottomNavigationBar />
<TooManyRelaysAlertDialog /> <TooManyRelaysAlertDialog />
<CreateWalletGuideToast />
</NotificationProvider> </NotificationProvider>
</CurrentRelaysProvider> </CurrentRelaysProvider>
</SecondaryPageContext.Provider> </SecondaryPageContext.Provider>
@ -382,6 +384,7 @@ export function PageManager({ maxStackSize = 5 }: { maxStackSize?: number }) {
</div> </div>
</div> </div>
<TooManyRelaysAlertDialog /> <TooManyRelaysAlertDialog />
<CreateWalletGuideToast />
</NotificationProvider> </NotificationProvider>
</CurrentRelaysProvider> </CurrentRelaysProvider>
</SecondaryPageContext.Provider> </SecondaryPageContext.Provider>

31
src/components/CreateWalletGuideToast/index.tsx

@ -0,0 +1,31 @@
import { toWallet } from '@/lib/link'
import { useSecondaryPage } from '@/PageManager'
import { useNostr } from '@/providers/NostrProvider'
import storage from '@/services/local-storage.service'
import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
export default function CreateWalletGuideToast() {
const { t } = useTranslation()
const { push } = useSecondaryPage()
const { profile } = useNostr()
useEffect(() => {
if (
profile &&
!profile.lightningAddress &&
!storage.hasShownCreateWalletGuideToast(profile.pubkey)
) {
toast(t('Set up your wallet to send and receive sats!'), {
action: {
label: t('Set up'),
onClick: () => push(toWallet())
}
})
storage.markCreateWalletGuideToastAsShown(profile.pubkey)
}
}, [profile])
return null
}

2
src/components/ui/button.tsx

@ -17,7 +17,7 @@ const buttonVariants = cva(
'secondary-2': 'bg-secondary text-secondary-foreground hover:bg-primary', 'secondary-2': 'bg-secondary text-secondary-foreground hover:bg-primary',
ghost: 'clickable hover:text-accent-foreground', ghost: 'clickable hover:text-accent-foreground',
'ghost-destructive': 'cursor-pointer hover:bg-destructive/20 text-destructive', 'ghost-destructive': 'cursor-pointer hover:bg-destructive/20 text-destructive',
link: 'text-primary underline-offset-4 hover:underline' link: 'text-foreground underline-offset-4 hover:underline'
}, },
size: { size: {
default: 'h-9 px-4 py-2', default: 'h-9 px-4 py-2',

1
src/constants.ts

@ -47,6 +47,7 @@ export const StorageKey = {
HIDE_CONTENT_MENTIONING_MUTED_USERS: 'hideContentMentioningMutedUsers', HIDE_CONTENT_MENTIONING_MUTED_USERS: 'hideContentMentioningMutedUsers',
NOTIFICATION_LIST_STYLE: 'notificationListStyle', NOTIFICATION_LIST_STYLE: 'notificationListStyle',
MEDIA_AUTO_LOAD_POLICY: 'mediaAutoLoadPolicy', MEDIA_AUTO_LOAD_POLICY: 'mediaAutoLoadPolicy',
SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS: 'shownCreateWalletGuideToastPubkeys',
MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated MEDIA_UPLOAD_SERVICE: 'mediaUploadService', // deprecated
HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated HIDE_UNTRUSTED_EVENTS: 'hideUntrustedEvents', // deprecated
ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated ACCOUNT_RELAY_LIST_EVENT_MAP: 'accountRelayListEventMap', // deprecated

23
src/i18n/locales/ar.ts

@ -421,6 +421,27 @@ export default {
'Write relays and {{count}} other relays': 'مرحلات الكتابة و {{count}} مرحل آخر', 'Write relays and {{count}} other relays': 'مرحلات الكتابة و {{count}} مرحل آخر',
'{{count}} relays': '{{count}} ريلايات', '{{count}} relays': '{{count}} ريلايات',
'Republishing...': 'جارٍ إعادة النشر...', 'Republishing...': 'جارٍ إعادة النشر...',
'Trending Notes': 'الملاحظات الرائجة' 'Trending Notes': 'الملاحظات الرائجة',
'Connected to': 'متصل بـ',
'Disconnect Wallet': 'قطع الاتصال بالمحفظة',
'Are you absolutely sure?': 'هل أنت متأكد تماماً؟',
'You will not be able to send zaps to others.': 'لن تتمكن من إرسال zaps للآخرين.',
Disconnect: 'قطع الاتصال',
'Start with a Rizful Vault': 'ابدأ بمحفظة Rizful',
'or other wallets': 'أو محافظ أخرى',
'Rizful Vault': 'محفظة Rizful',
'Rizful Vault connected!': 'تم توصيل محفظة Rizful!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'يمكنك الآن استخدام محفظة Rizful الخاصة بك لإرسال zap إلى ملاحظاتك والمبدعين المفضلين لديك.',
'Your Lightning Address': 'عنوان Lightning الخاص بك',
'New to Rizful?': 'جديد في Rizful؟',
'Sign up for Rizful': 'سجل في Rizful',
'If you already have a Rizful account, you can skip this step.':
'إذا كان لديك حساب Rizful بالفعل، يمكنك تخطي هذه الخطوة.',
'Get your one-time code': 'احصل على رمز الاستخدام مرة واحدة',
'Get code': 'احصل على الرمز',
'Connect to your Rizful Vault': 'الاتصال بمحفظة Rizful الخاصة بك',
'Paste your one-time code here': 'الصق رمز الاستخدام مرة واحدة هنا',
Connect: 'اتصال'
} }
} }

24
src/i18n/locales/de.ts

@ -433,6 +433,28 @@ export default {
'Write relays and {{count}} other relays': 'Schreib-Relays und {{count}} andere Relays', 'Write relays and {{count}} other relays': 'Schreib-Relays und {{count}} andere Relays',
'{{count}} relays': '{{count}} Relays', '{{count}} relays': '{{count}} Relays',
'Republishing...': 'Wird erneut veröffentlicht...', 'Republishing...': 'Wird erneut veröffentlicht...',
'Trending Notes': 'Trendende Notizen' 'Trending Notes': 'Trendende Notizen',
'Connected to': 'Verbunden mit',
'Disconnect Wallet': 'Wallet trennen',
'Are you absolutely sure?': 'Bist du dir absolut sicher?',
'You will not be able to send zaps to others.':
'Du wirst keine Zaps mehr an andere senden können.',
Disconnect: 'Trennen',
'Start with a Rizful Vault': 'Starte mit einem Rizful Vault',
'or other wallets': 'oder andere Wallets',
'Rizful Vault': 'Rizful Vault',
'Rizful Vault connected!': 'Rizful Vault verbunden!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'Du kannst jetzt dein Rizful Vault verwenden, um deine Lieblingsnotizen und -ersteller zu zapen.',
'Your Lightning Address': 'Deine Lightning-Adresse',
'New to Rizful?': 'Neu bei Rizful?',
'Sign up for Rizful': 'Registriere dich bei Rizful',
'If you already have a Rizful account, you can skip this step.':
'Wenn du bereits ein Rizful-Konto hast, kannst du diesen Schritt überspringen.',
'Get your one-time code': 'Hole dir deinen Einmal-Code',
'Get code': 'Code holen',
'Connect to your Rizful Vault': 'Verbinde dich mit deinem Rizful Vault',
'Paste your one-time code here': 'Füge hier deinen Einmal-Code ein',
Connect: 'Verbinden'
} }
} }

23
src/i18n/locales/en.ts

@ -420,6 +420,27 @@ export default {
'Write relays and {{count}} other relays': 'Write relays and {{count}} other relays', 'Write relays and {{count}} other relays': 'Write relays and {{count}} other relays',
'{{count}} relays': '{{count}} relays', '{{count}} relays': '{{count}} relays',
'Republishing...': 'Republishing...', 'Republishing...': 'Republishing...',
'Trending Notes': 'Trending Notes' 'Trending Notes': 'Trending Notes',
'Connected to': 'Connected to',
'Disconnect Wallet': 'Disconnect Wallet',
'Are you absolutely sure?': 'Are you absolutely sure?',
'You will not be able to send zaps to others.': 'You will not be able to send zaps to others.',
Disconnect: 'Disconnect',
'Start with a Rizful Vault': 'Start with a Rizful Vault',
'or other wallets': 'or other wallets',
'Rizful Vault': 'Rizful Vault',
'Rizful Vault connected!': 'Rizful Vault connected!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'You can now use your Rizful Vault to zap your favorite notes and creators.',
'Your Lightning Address': 'Your Lightning Address',
'New to Rizful?': 'New to Rizful?',
'Sign up for Rizful': 'Sign up for Rizful',
'If you already have a Rizful account, you can skip this step.':
'If you already have a Rizful account, you can skip this step.',
'Get your one-time code': 'Get your one-time code',
'Get code': 'Get code',
'Connect to your Rizful Vault': 'Connect to your Rizful Vault',
'Paste your one-time code here': 'Paste your one-time code here',
Connect: 'Connect'
} }
} }

23
src/i18n/locales/es.ts

@ -428,6 +428,27 @@ export default {
'Write relays and {{count}} other relays': 'Relés de escritura y {{count}} otros relés', 'Write relays and {{count}} other relays': 'Relés de escritura y {{count}} otros relés',
'{{count}} relays': '{{count}} relés', '{{count}} relays': '{{count}} relés',
'Republishing...': 'Republicando...', 'Republishing...': 'Republicando...',
'Trending Notes': 'Notas de tendencia' 'Trending Notes': 'Notas de tendencia',
'Connected to': 'Conectado a',
'Disconnect Wallet': 'Desconectar billetera',
'Are you absolutely sure?': '¿Estás absolutamente seguro?',
'You will not be able to send zaps to others.': 'No podrás enviar zaps a otros.',
Disconnect: 'Desconectar',
'Start with a Rizful Vault': 'Comienza con una Bóveda Rizful',
'or other wallets': 'o otras billeteras',
'Rizful Vault': 'Bóveda Rizful',
'Rizful Vault connected!': '¡Bóveda Rizful conectada!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'Ahora puedes usar tu Bóveda Rizful para zapear tus notas y creadores favoritos.',
'Your Lightning Address': 'Tu Dirección Lightning',
'New to Rizful?': '¿Nuevo en Rizful?',
'Sign up for Rizful': 'Regístrate en Rizful',
'If you already have a Rizful account, you can skip this step.':
'Si ya tienes una cuenta de Rizful, puedes omitir este paso.',
'Get your one-time code': 'Obtén tu código de un solo uso',
'Get code': 'Obtener código',
'Connect to your Rizful Vault': 'Conéctate a tu Bóveda Rizful',
'Paste your one-time code here': 'Pega tu código de un solo uso aquí',
Connect: 'Conectar'
} }
} }

23
src/i18n/locales/fa.ts

@ -423,6 +423,27 @@ export default {
'Write relays and {{count}} other relays': 'رلههای نوشتن و {{count}} رله دیگر', 'Write relays and {{count}} other relays': 'رلههای نوشتن و {{count}} رله دیگر',
'{{count}} relays': '{{count}} رله', '{{count}} relays': '{{count}} رله',
'Republishing...': 'در حال بازنشر...', 'Republishing...': 'در حال بازنشر...',
'Trending Notes': 'یادداشتهای محبوب' 'Trending Notes': 'یادداشتهای محبوب',
'Connected to': 'متصل به',
'Disconnect Wallet': 'قطع اتصال کیف پول',
'Are you absolutely sure?': 'آیا کاملاً مطمئن هستید؟',
'You will not be able to send zaps to others.': 'شما قادر نخواهید بود به دیگران زپ ارسال کنید.',
Disconnect: 'قطع اتصال',
'Start with a Rizful Vault': 'شروع با Rizful Vault',
'or other wallets': 'یا کیف پولهای دیگر',
'Rizful Vault': 'Rizful Vault',
'Rizful Vault connected!': 'Rizful Vault متصل شد!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'اکنون میتوانید از Rizful Vault خود برای زپ کردن یادداشتها و سازندگان مورد علاقه خود استفاده کنید.',
'Your Lightning Address': 'آدرس لایتنینگ شما',
'New to Rizful?': 'جدید در Rizful؟',
'Sign up for Rizful': 'برای Rizful ثبت نام کنید',
'If you already have a Rizful account, you can skip this step.':
'اگر قبلاً حساب Rizful دارید، میتوانید این مرحله را رد کنید.',
'Get your one-time code': 'کد یکبار مصرف خود را دریافت کنید',
'Get code': 'دریافت کد',
'Connect to your Rizful Vault': 'اتصال به Rizful Vault خود',
'Paste your one-time code here': 'کد یکبار مصرف خود را اینجا بچسبانید',
Connect: 'اتصال'
} }
} }

24
src/i18n/locales/fr.ts

@ -432,6 +432,28 @@ export default {
'Write relays and {{count}} other relays': 'Relais d’écriture et {{count}} autres relais', 'Write relays and {{count}} other relays': 'Relais d’écriture et {{count}} autres relais',
'{{count}} relays': '{{count}} relais', '{{count}} relays': '{{count}} relais',
'Republishing...': 'Republication en cours...', 'Republishing...': 'Republication en cours...',
'Trending Notes': 'Notes tendance' 'Trending Notes': 'Notes tendance',
'Connected to': 'Connecté à',
'Disconnect Wallet': 'Déconnecter le portefeuille',
'Are you absolutely sure?': 'Êtes-vous absolument sûr ?',
'You will not be able to send zaps to others.':
'Vous ne pourrez plus envoyer de zaps aux autres.',
Disconnect: 'Déconnecter',
'Start with a Rizful Vault': 'Démarrer avec un coffre Rizful',
'or other wallets': 'ou d’autres portefeuilles',
'Rizful Vault': 'Coffre Rizful',
'Rizful Vault connected!': 'Coffre Rizful connecté !',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'Vous pouvez maintenant utiliser votre coffre Rizful pour zapper vos notes et créateurs préférés.',
'Your Lightning Address': 'Votre adresse Lightning',
'New to Rizful?': 'Nouveau sur Rizful ?',
'Sign up for Rizful': 'Inscrivez-vous sur Rizful',
'If you already have a Rizful account, you can skip this step.':
'Si vous avez déjà un compte Rizful, vous pouvez passer cette étape.',
'Get your one-time code': 'Obtenez votre code à usage unique',
'Get code': 'Obtenir le code',
'Connect to your Rizful Vault': 'Connectez-vous à votre coffre Rizful',
'Paste your one-time code here': 'Collez votre code à usage unique ici',
Connect: 'Connecter'
} }
} }

23
src/i18n/locales/hi.ts

@ -425,6 +425,27 @@ export default {
'Write relays and {{count}} other relays': 'रइट रि और {{count}} अनय रि', 'Write relays and {{count}} other relays': 'रइट रि और {{count}} अनय रि',
'{{count}} relays': '{{count}} रि', '{{count}} relays': '{{count}} रि',
'Republishing...': 'परकित कर रह...', 'Republishing...': 'परकित कर रह...',
'Trending Notes': 'टिग नस' 'Trending Notes': 'टिग नस',
'Connected to': 'स कनड',
'Disconnect Wallet': 'वट डिकनट कर',
'Are you absolutely sure?': 'क आप प तरह सिित ह?',
'You will not be able to send zaps to others.': 'आप दसरप नहज प।',
Disconnect: 'डिकनट कर',
'Start with a Rizful Vault': 'Rizful वट कथ श कर',
'or other wallets': 'य अनय वट',
'Rizful Vault': 'Rizful वट',
'Rizful Vault connected!': 'Rizful वट कनड!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'अब आप अपन Rizful वट क उपयग अपन पसस और किएटरस कप करनिए कर सकत।',
'Your Lightning Address': 'आपकइटनिग पत',
'New to Rizful?': 'Rizful म नय?',
'Sign up for Rizful': 'Rizful किए सइन अप कर',
'If you already have a Rizful account, you can skip this step.':
'यदि आपकस पहल एक Rizful अकट ह, त आप इस चरण क सकत।',
'Get your one-time code': 'अपन वन-टइम कड पत कर',
'Get code': 'कड पत कर',
'Connect to your Rizful Vault': 'अपन Rizful वट स कनट कर',
'Paste your one-time code here': 'अपन वन-टइम कड यहट कर',
Connect: 'कनट कर'
} }
} }

23
src/i18n/locales/it.ts

@ -428,6 +428,27 @@ export default {
'Write relays and {{count}} other relays': 'Relay di scrittura e {{count}} altri relay', 'Write relays and {{count}} other relays': 'Relay di scrittura e {{count}} altri relay',
'{{count}} relays': '{{count}} relay', '{{count}} relays': '{{count}} relay',
'Republishing...': 'Ricondivisione in corso...', 'Republishing...': 'Ricondivisione in corso...',
'Trending Notes': 'Note di tendenza' 'Trending Notes': 'Note di tendenza',
'Connected to': 'Connesso a',
'Disconnect Wallet': 'Disconnetti Wallet',
'Are you absolutely sure?': 'Sei assolutamente sicuro?',
'You will not be able to send zaps to others.': 'Non sarai in grado di inviare zaps ad altri.',
Disconnect: 'Disconnetti',
'Start with a Rizful Vault': 'Inizia con un Rizful Vault',
'or other wallets': 'o altri wallet',
'Rizful Vault': 'Rizful Vault',
'Rizful Vault connected!': 'Rizful Vault connesso!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'Puoi ora usare il tuo Rizful Vault per zappare le tue note e creatori preferiti.',
'Your Lightning Address': 'Il tuo Indirizzo Lightning',
'New to Rizful?': 'Nuovo a Rizful?',
'Sign up for Rizful': 'Iscriviti a Rizful',
'If you already have a Rizful account, you can skip this step.':
'Se hai già un account Rizful, puoi saltare questo passaggio.',
'Get your one-time code': 'Ottieni il tuo codice monouso',
'Get code': 'Ottieni codice',
'Connect to your Rizful Vault': 'Connettiti al tuo Rizful Vault',
'Paste your one-time code here': 'Incolla qui il tuo codice monouso',
Connect: 'Connetti'
} }
} }

23
src/i18n/locales/ja.ts

@ -424,6 +424,27 @@ export default {
'Write relays and {{count}} other relays': '書き込みリレーと他の {{count}} 個のリレー', 'Write relays and {{count}} other relays': '書き込みリレーと他の {{count}} 個のリレー',
'{{count}} relays': '{{count}} 個のリレー', '{{count}} relays': '{{count}} 個のリレー',
'Republishing...': '再公開中...', 'Republishing...': '再公開中...',
'Trending Notes': '注目のノート' 'Trending Notes': '注目のノート',
'Connected to': '接続先',
'Disconnect Wallet': 'ウォレットの接続を解除',
'Are you absolutely sure?': '本当に確かですか?',
'You will not be able to send zaps to others.': '他の人にZapを送信できなくなります。',
Disconnect: '接続解除',
'Start with a Rizful Vault': 'Rizful Vaultで始める',
'or other wallets': 'または他のウォレット',
'Rizful Vault': 'Rizful Vault',
'Rizful Vault connected!': 'Rizful Vaultが接続されました!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'これで、Rizful Vaultを使用してお気に入りのノートやクリエイターにZapを送信できます。',
'Your Lightning Address': 'あなたのライトニングアドレス',
'New to Rizful?': 'Rizfulを初めて利用しますか?',
'Sign up for Rizful': 'Rizfulにサインアップ',
'If you already have a Rizful account, you can skip this step.':
'すでにRizfulアカウントをお持ちの場合は、このステップをスキップできます。',
'Get your one-time code': 'ワンタイムコードを取得',
'Get code': 'コードを取得',
'Connect to your Rizful Vault': 'Rizful Vaultに接続',
'Paste your one-time code here': 'ここにワンタイムコードを貼り付けてください',
Connect: '接続'
} }
} }

23
src/i18n/locales/ko.ts

@ -424,6 +424,27 @@ export default {
'Write relays and {{count}} other relays': '쓰기 릴레이 및 기타 {{count}}개 릴레이', 'Write relays and {{count}} other relays': '쓰기 릴레이 및 기타 {{count}}개 릴레이',
'{{count}} relays': '{{count}}개 릴레이', '{{count}} relays': '{{count}}개 릴레이',
'Republishing...': '다시 게시 중...', 'Republishing...': '다시 게시 중...',
'Trending Notes': '트렌딩 노트' 'Trending Notes': '트렌딩 노트',
'Connected to': '연결됨',
'Disconnect Wallet': '지갑 연결 해제',
'Are you absolutely sure?': '정말 확실합니까?',
'You will not be able to send zaps to others.': '다른 사람에게 잽을 보낼 수 없습니다.',
Disconnect: '연결 해제',
'Start with a Rizful Vault': 'Rizful Vault로 시작하기',
'or other wallets': '또는 다른 지갑',
'Rizful Vault': 'Rizful Vault',
'Rizful Vault connected!': 'Rizful Vault 연결됨!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'이제 Rizful Vault를 사용하여 좋아하는 노트와 크리에이터에게 잽을 보낼 수 있습니다.',
'Your Lightning Address': '귀하의 라이트닝 주소',
'New to Rizful?': 'Rizful이 처음이신가요?',
'Sign up for Rizful': 'Rizful에 가입하기',
'If you already have a Rizful account, you can skip this step.':
'이미 Rizful 계정이 있다면 이 단계를 건너뛸 수 있습니다.',
'Get your one-time code': '일회용 코드 받기',
'Get code': '코드 받기',
'Connect to your Rizful Vault': 'Rizful Vault에 연결',
'Paste your one-time code here': '여기에 일회용 코드를 붙여넣기',
Connect: '연결'
} }
} }

23
src/i18n/locales/pl.ts

@ -428,6 +428,27 @@ export default {
'Write relays and {{count}} other relays': 'Przekaźniki zapisu i {{count}} innych przekaźników', 'Write relays and {{count}} other relays': 'Przekaźniki zapisu i {{count}} innych przekaźników',
'{{count}} relays': '{{count}} przekaźników', '{{count}} relays': '{{count}} przekaźników',
'Republishing...': 'Ponowne publikowanie...', 'Republishing...': 'Ponowne publikowanie...',
'Trending Notes': 'Popularne wpisy' 'Trending Notes': 'Popularne wpisy',
'Connected to': 'Połączono z',
'Disconnect Wallet': 'Odłącz portfel',
'Are you absolutely sure?': 'Czy jesteś całkowicie pewien?',
'You will not be able to send zaps to others.': 'Nie będziesz mógł wysyłać zapów innym.',
Disconnect: 'Odłącz',
'Start with a Rizful Vault': 'Zacznij od Rizful Vault',
'or other wallets': 'Lub inne portfele',
'Rizful Vault': 'Rizful Vault',
'Rizful Vault connected!': 'Rizful Vault połączony!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'Możesz teraz używać swojego Rizful Vault, aby zapować swoje ulubione notatki i twórców.',
'Your Lightning Address': 'Twój Lightning Adres',
'New to Rizful?': 'Nowy w Rizful?',
'Sign up for Rizful': 'Zarejestruj się w Rizful',
'If you already have a Rizful account, you can skip this step.':
'Jeśli masz już konto Rizful, możesz pominąć ten krok.',
'Get your one-time code': 'Uzyskaj swój jednorazowy kod',
'Get code': 'Uzyskaj kod',
'Connect to your Rizful Vault': 'Połącz się ze swoim Rizful Vault',
'Paste your one-time code here': 'Wklej tutaj swój jednorazowy kod',
Connect: 'Połącz'
} }
} }

23
src/i18n/locales/pt-BR.ts

@ -425,6 +425,27 @@ export default {
'Write relays and {{count}} other relays': 'Relays de escrita e {{count}} outros relays', 'Write relays and {{count}} other relays': 'Relays de escrita e {{count}} outros relays',
'{{count}} relays': '{{count}} relays', '{{count}} relays': '{{count}} relays',
'Republishing...': 'Republicando...', 'Republishing...': 'Republicando...',
'Trending Notes': 'Notas em tendência' 'Trending Notes': 'Notas em tendência',
'Connected to': 'Conectado a',
'Disconnect Wallet': 'Desconectar carteira',
'Are you absolutely sure?': 'Você tem certeza absoluta?',
'You will not be able to send zaps to others.': 'Você não poderá enviar zaps para outros.',
Disconnect: 'Desconectar',
'Start with a Rizful Vault': 'Comece com um Cofre Rizful',
'or other wallets': 'ou outras carteiras',
'Rizful Vault': 'Cofre Rizful',
'Rizful Vault connected!': 'Cofre Rizful conectado!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'Você pode agora usar seu Cofre Rizful para zapear suas notas e criadores favoritos.',
'Your Lightning Address': 'Seu Endereço Lightning',
'New to Rizful?': 'Novo no Rizful?',
'Sign up for Rizful': 'Inscreva-se no Rizful',
'If you already have a Rizful account, you can skip this step.':
'Se você já tem uma conta Rizful, pode pular esta etapa.',
'Get your one-time code': 'Obtenha seu código único',
'Get code': 'Obter código',
'Connect to your Rizful Vault': 'Conecte-se ao seu Cofre Rizful',
'Paste your one-time code here': 'Cole seu código único aqui',
Connect: 'Conectar'
} }
} }

23
src/i18n/locales/pt-PT.ts

@ -428,6 +428,27 @@ export default {
'Write relays and {{count}} other relays': 'Relays de escrita e {{count}} outros relays', 'Write relays and {{count}} other relays': 'Relays de escrita e {{count}} outros relays',
'{{count}} relays': '{{count}} relays', '{{count}} relays': '{{count}} relays',
'Republishing...': 'Republicando...', 'Republishing...': 'Republicando...',
'Trending Notes': 'Notas em Tendência' 'Trending Notes': 'Notas em Tendência',
'Connected to': 'Conectado a',
'Disconnect Wallet': 'Desconectar Carteira',
'Are you absolutely sure?': 'Tem certeza absoluta?',
'You will not be able to send zaps to others.': 'Você não poderá enviar zaps para outros.',
Disconnect: 'Desconectar',
'Start with a Rizful Vault': 'Comece com um Cofre Rizful',
'or other wallets': 'outras carteiras',
'Rizful Vault': 'Cofre Rizful',
'Rizful Vault connected!': 'Cofre Rizful conectado!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'Agora você pode usar seu Cofre Rizful para zapear suas notas e criadores favoritos.',
'Your Lightning Address': 'Seu Endereço Lightning',
'New to Rizful?': 'Novo no Rizful?',
'Sign up for Rizful': 'Inscreva-se no Rizful',
'If you already have a Rizful account, you can skip this step.':
'Se você já tem uma conta Rizful, pode pular esta etapa.',
'Get your one-time code': 'Obtenha seu código único',
'Get code': 'Obter código',
'Connect to your Rizful Vault': 'Conecte-se ao seu Cofre Rizful',
'Paste your one-time code here': 'Cole seu código único aqui',
Connect: 'Conectar'
} }
} }

23
src/i18n/locales/ru.ts

@ -430,6 +430,27 @@ export default {
'Ретрансляторы записи и {{count}} других ретрансляторов', 'Ретрансляторы записи и {{count}} других ретрансляторов',
'{{count}} relays': '{{count}} ретрансляторов', '{{count}} relays': '{{count}} ретрансляторов',
'Republishing...': 'Ретрансляция...', 'Republishing...': 'Ретрансляция...',
'Trending Notes': 'Популярные заметки' 'Trending Notes': 'Популярные заметки',
'Connected to': 'Подключено к',
'Disconnect Wallet': 'Отключить кошелёк',
'Are you absolutely sure?': 'Вы абсолютно уверены?',
'You will not be able to send zaps to others.': 'Вы не сможете отправлять запы другим.',
Disconnect: 'Отключить',
'Start with a Rizful Vault': 'Начать с Rizful Vault',
'or other wallets': 'или другие кошельки',
'Rizful Vault': 'Rizful Vault',
'Rizful Vault connected!': 'Rizful Vault подключён!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'Теперь вы можете использовать свой Rizful Vault, чтобы заппить ваши любимые заметки и создателей.',
'Your Lightning Address': 'Ваш Lightning-адрес',
'New to Rizful?': 'Новичок в Rizful?',
'Sign up for Rizful': 'Зарегистрируйтесь на Rizful',
'If you already have a Rizful account, you can skip this step.':
'Если у вас уже есть аккаунт Rizful, вы можете пропустить этот шаг.',
'Get your one-time code': 'Получите ваш одноразовый код',
'Get code': 'Получить код',
'Connect to your Rizful Vault': 'Подключитесь к вашему Rizful Vault',
'Paste your one-time code here': 'Вставьте ваш одноразовый код здесь',
Connect: 'Подключить'
} }
} }

23
src/i18n/locales/th.ts

@ -419,6 +419,27 @@ export default {
'Write relays and {{count}} other relays': 'รเลยเขยนและรเลยน ๆ {{count}} ตว', 'Write relays and {{count}} other relays': 'รเลยเขยนและรเลยน ๆ {{count}} ตว',
'{{count}} relays': 'รเลย {{count}} ตว', '{{count}} relays': 'รเลย {{count}} ตว',
'Republishing...': 'กำลงเผยแพรำ...', 'Republishing...': 'กำลงเผยแพรำ...',
'Trending Notes': 'โนตยอดนยม' 'Trending Notes': 'โนตยอดนยม',
'Connected to': 'เชอมตอกบ',
'Disconnect Wallet': 'ตดการเชอมตอกระเปาสตางค',
'Are you absolutely sure?': 'คณแนใจอยางยงหรอไม?',
'You will not be able to send zaps to others.': 'คณจะไมสามารถสงซาตสไปยงผนได',
Disconnect: 'ตดการเชอมตอ',
'Start with a Rizful Vault': 'เรมตนดวย Rizful Vault',
'or other wallets': 'หรอกระเปาสตางคนๆ',
'Rizful Vault': 'Rizful Vault',
'Rizful Vault connected!': 'Rizful Vault เชอมตอแลว!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'คณสามารถใช Rizful Vault ของคณเพอสงซาตสไปยงโนตและผสรางทณชนชอบไดแลว',
'Your Lightning Address': 'ทอย Lightning ของคณ',
'New to Rizful?': 'ใหมบ Rizful?',
'Sign up for Rizful': 'สมครสมาชก Rizful',
'If you already have a Rizful account, you can skip this step.':
'หากคณมญช Rizful อยแลว คณสามารถขามขนตอนนได',
'Get your one-time code': 'รบรหสใชครงเดยวของคณ',
'Get code': 'รบรหส',
'Connect to your Rizful Vault': 'เชอมตอกบ Rizful Vault ของคณ',
'Paste your one-time code here': 'วางรหสใชครงเดยวของคณท',
Connect: 'เชอมตอ'
} }
} }

23
src/i18n/locales/zh.ts

@ -417,6 +417,27 @@ export default {
'Write relays and {{count}} other relays': '写服务器和其他 {{count}} 个服务器', 'Write relays and {{count}} other relays': '写服务器和其他 {{count}} 个服务器',
'{{count}} relays': '{{count}} 个服务器', '{{count}} relays': '{{count}} 个服务器',
'Republishing...': '正在重新发布...', 'Republishing...': '正在重新发布...',
'Trending Notes': '热门笔记' 'Trending Notes': '热门笔记',
'Connected to': '已连接到',
'Disconnect Wallet': '断开钱包连接',
'Are you absolutely sure?': '您确定吗?',
'You will not be able to send zaps to others.': '您将无法向他人发送打闪。',
Disconnect: '断开连接',
'Start with a Rizful Vault': '从 Rizful 钱包开始',
'or other wallets': '或其他钱包',
'Rizful Vault': 'Rizful 钱包',
'Rizful Vault connected!': 'Rizful 钱包已连接!',
'You can now use your Rizful Vault to zap your favorite notes and creators.':
'您现在可以使用您的 Rizful 钱包为您喜欢的笔记和创作者打闪。',
'Your Lightning Address': '您的闪电地址',
'New to Rizful?': '第一次使用 Rizful?',
'Sign up for Rizful': '注册 Rizful',
'If you already have a Rizful account, you can skip this step.':
'如果您已经有一个 Rizful 账户,可以跳过此步骤。',
'Get your one-time code': '获取一次性代码',
'Get code': '获取代码',
'Connect to your Rizful Vault': '连接到您的 Rizful 钱包',
'Paste your one-time code here': '将您的一次性代码粘贴到此处',
Connect: '连接'
} }
} }

1
src/lib/link.ts

@ -74,6 +74,7 @@ export const toProfileEditor = () => '/profile-editor'
export const toRelay = (url: string) => `/relays/${encodeURIComponent(url)}` export const toRelay = (url: string) => `/relays/${encodeURIComponent(url)}`
export const toRelayReviews = (url: string) => `/relays/${encodeURIComponent(url)}/reviews` export const toRelayReviews = (url: string) => `/relays/${encodeURIComponent(url)}/reviews`
export const toMuteList = () => '/mutes' export const toMuteList = () => '/mutes'
export const toRizful = () => '/rizful'
export const toChachiChat = (relay: string, d: string) => { export const toChachiChat = (relay: string, d: string) => {
return `https://chachi.chat/${relay.replace(/^wss?:\/\//, '').replace(/\/$/, '')}/${d}` return `https://chachi.chat/${relay.replace(/^wss?:\/\//, '').replace(/\/$/, '')}/${d}`

35
src/pages/secondary/ProfileEditorPage/index.tsx

@ -65,21 +65,6 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
return return
} }
let lud06 = profile.lud06
let lud16 = profile.lud16
if (lightningAddress) {
if (isEmail(lightningAddress)) {
lud16 = lightningAddress
} else if (lightningAddress.startsWith('lnurl')) {
lud06 = lightningAddress
} else {
setLightningAddressError(t('Invalid Lightning Address'))
return
}
}
setSaving(true)
setHasChanged(false)
const oldProfileContent = profileEvent ? JSON.parse(profileEvent.content) : {} const oldProfileContent = profileEvent ? JSON.parse(profileEvent.content) : {}
const newProfileContent = { const newProfileContent = {
...oldProfileContent, ...oldProfileContent,
@ -90,10 +75,24 @@ const ProfileEditorPage = forwardRef(({ index }: { index?: number }, ref) => {
website, website,
nip05, nip05,
banner, banner,
picture: avatar, picture: avatar
lud06, }
lud16
if (lightningAddress) {
if (isEmail(lightningAddress)) {
newProfileContent.lud16 = lightningAddress
} else if (lightningAddress.startsWith('lnurl')) {
newProfileContent.lud06 = lightningAddress
} else {
setLightningAddressError(t('Invalid Lightning Address'))
return
}
} else {
delete newProfileContent.lud16
} }
setSaving(true)
setHasChanged(false)
const profileDraftEvent = createProfileDraftEvent( const profileDraftEvent = createProfileDraftEvent(
JSON.stringify(newProfileContent), JSON.stringify(newProfileContent),
profileEvent?.tags profileEvent?.tags

203
src/pages/secondary/RizfulPage/index.tsx

@ -0,0 +1,203 @@
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { createProfileDraftEvent } from '@/lib/draft-event'
import { isEmail } from '@/lib/utils'
import { useNostr } from '@/providers/NostrProvider'
import { useZap } from '@/providers/ZapProvider'
import { connectNWC, WebLNProviders } from '@getalby/bitcoin-connect'
import { Check, CheckCircle2, Copy, ExternalLink, Loader2 } from 'lucide-react'
import { forwardRef, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
const RIZFUL_URL = 'https://rizful.com'
const RIZFUL_SIGNUP_URL = `${RIZFUL_URL}/create-account`
const RIZFUL_GET_TOKEN_URL = `${RIZFUL_URL}/nostr_onboarding_auth_token/get_token`
const RIZFUL_TOKEN_EXCHANGE_URL = `${RIZFUL_URL}/nostr_onboarding_auth_token/post_for_secrets`
const RizfulPage = forwardRef(({ index }: { index?: number }, ref) => {
const { t } = useTranslation()
const { pubkey, profile, profileEvent, publish, updateProfileEvent } = useNostr()
const { provider } = useZap()
const [token, setToken] = useState('')
const [connecting, setConnecting] = useState(false)
const [connected, setConnected] = useState(false)
const [copiedLightningAddress, setCopiedLightningAddress] = useState(false)
const [lightningAddress, setLightningAddress] = useState('')
useEffect(() => {
if (provider instanceof WebLNProviders.NostrWebLNProvider) {
const lud16 = provider.client.lud16
const domain = lud16?.split('@')[1]
if (domain !== 'rizful.com') return
if (lud16) {
setConnected(true)
setLightningAddress(lud16)
}
}
}, [provider])
const updateUserProfile = async (address: string) => {
try {
if (address === profile?.lightningAddress) {
return
}
const profileContent = profileEvent ? JSON.parse(profileEvent.content) : {}
if (isEmail(address)) {
profileContent.lud16 = address
} else if (address.startsWith('lnurl')) {
profileContent.lud06 = address
} else {
throw new Error(t('Invalid Lightning Address'))
}
if (!profileContent.nip05) {
profileContent.nip05 = address
}
const profileDraftEvent = createProfileDraftEvent(
JSON.stringify(profileContent),
profileEvent?.tags
)
const newProfileEvent = await publish(profileDraftEvent)
await updateProfileEvent(newProfileEvent)
} catch (e: unknown) {
toast.error(e instanceof Error ? e.message : String(e))
}
}
const connectRizful = async () => {
setConnecting(true)
try {
const r = await fetch(RIZFUL_TOKEN_EXCHANGE_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'omit',
body: JSON.stringify({
secret_code: token.trim(),
nostr_public_key: pubkey
})
})
if (!r.ok) {
const errorText = await r.text()
throw new Error(errorText || 'Exchange failed')
}
const j = (await r.json()) as {
nwc_uri?: string
lightning_address?: string
}
if (j.nwc_uri) {
connectNWC(j.nwc_uri)
}
if (j.lightning_address) {
updateUserProfile(j.lightning_address)
}
} catch (e: unknown) {
toast.error(e instanceof Error ? e.message : String(e))
} finally {
setConnecting(false)
}
}
if (connected) {
return (
<SecondaryPageLayout ref={ref} index={index} title={t('Rizful Vault')}>
<div className="px-4 pt-3 space-y-6 flex flex-col items-center">
<CheckCircle2 className="size-40 fill-green-400 text-background" />
<div className="font-semibold text-2xl">{t('Rizful Vault connected!')}</div>
<div className="text-center text-sm text-muted-foreground">
{t('You can now use your Rizful Vault to zap your favorite notes and creators.')}
</div>
{lightningAddress && (
<div className="flex flex-col items-center gap-2">
<div>{t('Your Lightning Address')}:</div>
<div
className="font-semibold text-lg rounded-lg px-4 py-1 flex justify-center items-center gap-2 cursor-pointer hover:bg-accent/80"
onClick={() => {
navigator.clipboard.writeText(lightningAddress)
setCopiedLightningAddress(true)
setTimeout(() => setCopiedLightningAddress(false), 2000)
}}
>
{lightningAddress}{' '}
{copiedLightningAddress ? (
<Check className="size-4" />
) : (
<Copy className="size-4" />
)}
</div>
</div>
)}
</div>
</SecondaryPageLayout>
)
}
return (
<SecondaryPageLayout ref={ref} index={index} title={t('Rizful Vault')}>
<div className="px-4 pt-3 space-y-6">
<div className="space-y-2">
<div className="font-semibold">1. {t('New to Rizful?')}</div>
<Button
className="bg-lime-500 hover:bg-lime-500/90 w-64"
onClick={() => window.open(RIZFUL_SIGNUP_URL, '_blank')}
>
{t('Sign up for Rizful')} <ExternalLink />
</Button>
<div className="text-sm text-muted-foreground">
{t('If you already have a Rizful account, you can skip this step.')}
</div>
</div>
<div className="space-y-2">
<div className="font-semibold">2. {t('Get your one-time code')}</div>
<Button
className="bg-orange-500 hover:bg-orange-500/90 w-64"
onClick={() => openPopup(RIZFUL_GET_TOKEN_URL, 'rizful_codes')}
>
{t('Get code')}
<ExternalLink />
</Button>
</div>
<div className="space-y-2">
<div className="font-semibold">3. {t('Connect to your Rizful Vault')}</div>
<Input
placeholder={t('Paste your one-time code here')}
value={token}
onChange={(e) => {
setToken(e.target.value.trim())
}}
/>
<Button
className="bg-sky-500 hover:bg-sky-500/90 w-64"
disabled={!token || connecting}
onClick={() => connectRizful()}
>
{connecting && <Loader2 className="animate-spin" />}
{t('Connect')}
</Button>
</div>
</div>
</SecondaryPageLayout>
)
})
RizfulPage.displayName = 'RizfulPage'
export default RizfulPage
function openPopup(url: string, name: string, width = 520, height = 700) {
const left = Math.max((window.screenX || 0) + (window.innerWidth - width) / 2, 0)
const top = Math.max((window.screenY || 0) + (window.innerHeight - height) / 2, 0)
return window.open(
url,
name,
`width=${width},height=${height},left=${left},top=${top},resizable=yes,scrollbars=yes,menubar=no,toolbar=no,location=no,status=no`
)
}

32
src/pages/secondary/SettingsPage/index.tsx

@ -49,20 +49,24 @@ const SettingsPage = forwardRef(({ index }: { index?: number }, ref) => {
</div> </div>
<ChevronRight /> <ChevronRight />
</SettingItem> </SettingItem>
<SettingItem className="clickable" onClick={() => push(toTranslation())}> {!!pubkey && (
<div className="flex items-center gap-4"> <SettingItem className="clickable" onClick={() => push(toTranslation())}>
<Languages /> <div className="flex items-center gap-4">
<div>{t('Translation')}</div> <Languages />
</div> <div>{t('Translation')}</div>
<ChevronRight /> </div>
</SettingItem> <ChevronRight />
<SettingItem className="clickable" onClick={() => push(toWallet())}> </SettingItem>
<div className="flex items-center gap-4"> )}
<Wallet /> {!!pubkey && (
<div>{t('Wallet')}</div> <SettingItem className="clickable" onClick={() => push(toWallet())}>
</div> <div className="flex items-center gap-4">
<ChevronRight /> <Wallet />
</SettingItem> <div>{t('Wallet')}</div>
</div>
<ChevronRight />
</SettingItem>
)}
{!!pubkey && ( {!!pubkey && (
<SettingItem className="clickable" onClick={() => push(toPostSettings())}> <SettingItem className="clickable" onClick={() => push(toPostSettings())}>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">

19
src/pages/secondary/WalletPage/LightningAddressInput.tsx

@ -28,26 +28,21 @@ export default function LightningAddressInput() {
const handleSave = async () => { const handleSave = async () => {
setSaving(true) setSaving(true)
let lud06 = profile.lud06 const profileContent = profileEvent ? JSON.parse(profileEvent.content) : {}
let lud16 = profile.lud16
if (lightningAddress.startsWith('lnurl')) { if (lightningAddress.startsWith('lnurl')) {
lud06 = lightningAddress profileContent.lud06 = lightningAddress
} else if (isEmail(lightningAddress)) { } else if (isEmail(lightningAddress)) {
lud16 = lightningAddress profileContent.lud16 = lightningAddress
} else { } else if (lightningAddress) {
toast.error(t('Invalid Lightning Address. Please enter a valid Lightning Address or LNURL.')) toast.error(t('Invalid Lightning Address. Please enter a valid Lightning Address or LNURL.'))
setSaving(false) setSaving(false)
return return
} else {
delete profileContent.lud16
} }
const oldProfileContent = profileEvent ? JSON.parse(profileEvent.content) : {}
const newProfileContent = {
...oldProfileContent,
lud06,
lud16
}
const profileDraftEvent = createProfileDraftEvent( const profileDraftEvent = createProfileDraftEvent(
JSON.stringify(newProfileContent), JSON.stringify(profileContent),
profileEvent?.tags profileEvent?.tags
) )
const newProfileEvent = await publish(profileDraftEvent) const newProfileEvent = await publish(profileDraftEvent)

75
src/pages/secondary/WalletPage/index.tsx

@ -1,5 +1,20 @@
import { useSecondaryPage } from '@/PageManager'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
AlertDialogTrigger
} from '@/components/ui/alert-dialog'
import { Button } from '@/components/ui/button'
import SecondaryPageLayout from '@/layouts/SecondaryPageLayout' import SecondaryPageLayout from '@/layouts/SecondaryPageLayout'
import { Button as BcButton } from '@getalby/bitcoin-connect-react' import { toRizful } from '@/lib/link'
import { useZap } from '@/providers/ZapProvider'
import { disconnect, launchModal } from '@getalby/bitcoin-connect-react'
import { forwardRef } from 'react' import { forwardRef } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import DefaultZapAmountInput from './DefaultZapAmountInput' import DefaultZapAmountInput from './DefaultZapAmountInput'
@ -9,16 +24,60 @@ import QuickZapSwitch from './QuickZapSwitch'
const WalletPage = forwardRef(({ index }: { index?: number }, ref) => { const WalletPage = forwardRef(({ index }: { index?: number }, ref) => {
const { t } = useTranslation() const { t } = useTranslation()
const { push } = useSecondaryPage()
const { isWalletConnected, walletInfo } = useZap()
return ( return (
<SecondaryPageLayout ref={ref} index={index} title={t('Wallet')}> <SecondaryPageLayout ref={ref} index={index} title={t('Wallet')}>
<div className="px-4 pt-3 space-y-4"> {isWalletConnected ? (
<BcButton /> <div className="px-4 pt-3 space-y-4">
<LightningAddressInput /> <div>
<DefaultZapAmountInput /> {walletInfo?.node.alias && (
<DefaultZapCommentInput /> <div className="mb-2">
<QuickZapSwitch /> {t('Connected to')} <strong>{walletInfo.node.alias}</strong>
</div> </div>
)}
<AlertDialog>
<AlertDialogTrigger asChild>
<Button variant="destructive">{t('Disconnect Wallet')}</Button>
</AlertDialogTrigger>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t('Are you absolutely sure?')}</AlertDialogTitle>
<AlertDialogDescription>
{t('You will not be able to send zaps to others.')}
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel>{t('Cancel')}</AlertDialogCancel>
<AlertDialogAction variant="destructive" onClick={() => disconnect()}>
{t('Disconnect')}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</div>
<DefaultZapAmountInput />
<DefaultZapCommentInput />
<QuickZapSwitch />
<LightningAddressInput />
</div>
) : (
<div className="px-4 pt-3 flex items-center gap-2">
<Button className="bg-foreground hover:bg-foreground/90" onClick={() => push(toRizful())}>
{t('Start with a Rizful Vault')}
</Button>
<Button
variant="link"
className="text-muted-foreground hover:text-foreground px-0"
onClick={() => {
launchModal()
}}
>
{t('or other wallets')}
</Button>
</div>
)}
</SecondaryPageLayout> </SecondaryPageLayout>
) )
}) })

34
src/providers/ZapProvider.tsx

@ -1,7 +1,13 @@
import lightningService from '@/services/lightning.service'
import storage from '@/services/local-storage.service' import storage from '@/services/local-storage.service'
import { createContext, useContext, useState } from 'react' import { onConnected, onDisconnected } from '@getalby/bitcoin-connect-react'
import { GetInfoResponse, WebLNProvider } from '@webbtc/webln-types'
import { createContext, useContext, useEffect, useState } from 'react'
type TZapContext = { type TZapContext = {
isWalletConnected: boolean
provider: WebLNProvider | null
walletInfo: GetInfoResponse | null
defaultZapSats: number defaultZapSats: number
updateDefaultSats: (sats: number) => void updateDefaultSats: (sats: number) => void
defaultZapComment: string defaultZapComment: string
@ -24,6 +30,29 @@ export function ZapProvider({ children }: { children: React.ReactNode }) {
const [defaultZapSats, setDefaultZapSats] = useState<number>(storage.getDefaultZapSats()) const [defaultZapSats, setDefaultZapSats] = useState<number>(storage.getDefaultZapSats())
const [defaultZapComment, setDefaultZapComment] = useState<string>(storage.getDefaultZapComment()) const [defaultZapComment, setDefaultZapComment] = useState<string>(storage.getDefaultZapComment())
const [quickZap, setQuickZap] = useState<boolean>(storage.getQuickZap()) const [quickZap, setQuickZap] = useState<boolean>(storage.getQuickZap())
const [isWalletConnected, setIsWalletConnected] = useState(false)
const [provider, setProvider] = useState<WebLNProvider | null>(null)
const [walletInfo, setWalletInfo] = useState<GetInfoResponse | null>(null)
useEffect(() => {
const unSubOnConnected = onConnected((provider) => {
setIsWalletConnected(true)
setWalletInfo(null)
setProvider(provider)
lightningService.provider = provider
provider.getInfo().then(setWalletInfo)
})
const unSubOnDisconnected = onDisconnected(() => {
setIsWalletConnected(false)
setProvider(null)
lightningService.provider = null
})
return () => {
unSubOnConnected()
unSubOnDisconnected()
}
}, [])
const updateDefaultSats = (sats: number) => { const updateDefaultSats = (sats: number) => {
storage.setDefaultZapSats(sats) storage.setDefaultZapSats(sats)
@ -43,6 +72,9 @@ export function ZapProvider({ children }: { children: React.ReactNode }) {
return ( return (
<ZapContext.Provider <ZapContext.Provider
value={{ value={{
isWalletConnected,
provider,
walletInfo,
defaultZapSats, defaultZapSats,
updateDefaultSats, updateDefaultSats,
defaultZapComment, defaultZapComment,

4
src/routes.tsx

@ -13,6 +13,7 @@ import ProfilePage from './pages/secondary/ProfilePage'
import RelayPage from './pages/secondary/RelayPage' import RelayPage from './pages/secondary/RelayPage'
import RelayReviewsPage from './pages/secondary/RelayReviewsPage' import RelayReviewsPage from './pages/secondary/RelayReviewsPage'
import RelaySettingsPage from './pages/secondary/RelaySettingsPage' import RelaySettingsPage from './pages/secondary/RelaySettingsPage'
import RizfulPage from './pages/secondary/RizfulPage'
import SearchPage from './pages/secondary/SearchPage' import SearchPage from './pages/secondary/SearchPage'
import SettingsPage from './pages/secondary/SettingsPage' import SettingsPage from './pages/secondary/SettingsPage'
import TranslationPage from './pages/secondary/TranslationPage' import TranslationPage from './pages/secondary/TranslationPage'
@ -35,7 +36,8 @@ const ROUTES = [
{ path: '/settings/general', element: <GeneralSettingsPage /> }, { path: '/settings/general', element: <GeneralSettingsPage /> },
{ path: '/settings/translation', element: <TranslationPage /> }, { path: '/settings/translation', element: <TranslationPage /> },
{ path: '/profile-editor', element: <ProfileEditorPage /> }, { path: '/profile-editor', element: <ProfileEditorPage /> },
{ path: '/mutes', element: <MuteListPage /> } { path: '/mutes', element: <MuteListPage /> },
{ path: '/rizful', element: <RizfulPage /> }
] ]
export const routes = ROUTES.map(({ path, element }) => ({ export const routes = ROUTES.map(({ path, element }) => ({

15
src/services/lightning.service.ts

@ -1,12 +1,7 @@
import { BIG_RELAY_URLS, CODY_PUBKEY, JUMBLE_PUBKEY } from '@/constants' import { BIG_RELAY_URLS, CODY_PUBKEY, JUMBLE_PUBKEY } from '@/constants'
import { getZapInfoFromEvent } from '@/lib/event-metadata' import { getZapInfoFromEvent } from '@/lib/event-metadata'
import { TProfile } from '@/types' import { TProfile } from '@/types'
import { import { init, launchPaymentModal } from '@getalby/bitcoin-connect-react'
init,
launchPaymentModal,
onConnected,
onDisconnected
} from '@getalby/bitcoin-connect-react'
import { Invoice } from '@getalby/lightning-tools' import { Invoice } from '@getalby/lightning-tools'
import { bech32 } from '@scure/base' import { bech32 } from '@scure/base'
import { WebLNProvider } from '@webbtc/webln-types' import { WebLNProvider } from '@webbtc/webln-types'
@ -23,7 +18,7 @@ const OFFICIAL_PUBKEYS = [JUMBLE_PUBKEY, CODY_PUBKEY]
class LightningService { class LightningService {
static instance: LightningService static instance: LightningService
private provider: WebLNProvider | null = null provider: WebLNProvider | null = null
private recentSupportersCache: TRecentSupporter[] | null = null private recentSupportersCache: TRecentSupporter[] | null = null
constructor() { constructor() {
@ -33,12 +28,6 @@ class LightningService {
appName: 'Jumble', appName: 'Jumble',
showBalance: false showBalance: false
}) })
onConnected((provider) => {
this.provider = provider
})
onDisconnected(() => {
this.provider = null
})
} }
return LightningService.instance return LightningService.instance
} }

23
src/services/local-storage.service.ts

@ -47,6 +47,7 @@ class LocalStorageService {
private hideContentMentioningMutedUsers: boolean = false private hideContentMentioningMutedUsers: boolean = false
private notificationListStyle: TNotificationStyle = NOTIFICATION_LIST_STYLE.DETAILED private notificationListStyle: TNotificationStyle = NOTIFICATION_LIST_STYLE.DETAILED
private mediaAutoLoadPolicy: TMediaAutoLoadPolicy = MEDIA_AUTO_LOAD_POLICY.ALWAYS private mediaAutoLoadPolicy: TMediaAutoLoadPolicy = MEDIA_AUTO_LOAD_POLICY.ALWAYS
private shownCreateWalletGuideToastPubkeys: Set<string> = new Set()
constructor() { constructor() {
if (!LocalStorageService.instance) { if (!LocalStorageService.instance) {
@ -185,6 +186,13 @@ class LocalStorageService {
this.mediaAutoLoadPolicy = mediaAutoLoadPolicy as TMediaAutoLoadPolicy this.mediaAutoLoadPolicy = mediaAutoLoadPolicy as TMediaAutoLoadPolicy
} }
const shownCreateWalletGuideToastPubkeysStr = window.localStorage.getItem(
StorageKey.SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS
)
this.shownCreateWalletGuideToastPubkeys = shownCreateWalletGuideToastPubkeysStr
? new Set(JSON.parse(shownCreateWalletGuideToastPubkeysStr))
: new Set()
// Clean up deprecated data // Clean up deprecated data
window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP) window.localStorage.removeItem(StorageKey.ACCOUNT_PROFILE_EVENT_MAP)
window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP) window.localStorage.removeItem(StorageKey.ACCOUNT_FOLLOW_LIST_EVENT_MAP)
@ -453,6 +461,21 @@ class LocalStorageService {
this.mediaAutoLoadPolicy = policy this.mediaAutoLoadPolicy = policy
window.localStorage.setItem(StorageKey.MEDIA_AUTO_LOAD_POLICY, policy) window.localStorage.setItem(StorageKey.MEDIA_AUTO_LOAD_POLICY, policy)
} }
hasShownCreateWalletGuideToast(pubkey: string) {
return this.shownCreateWalletGuideToastPubkeys.has(pubkey)
}
markCreateWalletGuideToastAsShown(pubkey: string) {
if (this.shownCreateWalletGuideToastPubkeys.has(pubkey)) {
return
}
this.shownCreateWalletGuideToastPubkeys.add(pubkey)
window.localStorage.setItem(
StorageKey.SHOWN_CREATE_WALLET_GUIDE_TOAST_PUBKEYS,
JSON.stringify(Array.from(this.shownCreateWalletGuideToastPubkeys))
)
}
} }
const instance = new LocalStorageService() const instance = new LocalStorageService()

Loading…
Cancel
Save