aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2025-06-23 08:49:49 -0700
committerAdam Soltys <[email protected]>2025-06-23 08:49:49 -0700
commit1e8567796d1f28fa8c47664c7d1e34a57d820aca (patch)
tree1458e3e115cb6c1667c757223184163603992b3f /src
parent759e6a0530a7092ae8f2294f1ec1a6fb5e77a7de (diff)
downloadcoinos-ui-1e8567796d1f28fa8c47664c7d1e34a57d820aca.tar.gz
coinos-ui-1e8567796d1f28fa8c47664c7d1e34a57d820aca.zip
user lnurl
Diffstat (limited to 'src')
-rw-r--r--src/locales/en.json1077
-rw-r--r--src/routes/(app)/[username]/(profile)/+layout.svelte28
-rw-r--r--src/routes/(app)/ln/[lnurl]/+page.server.ts51
-rw-r--r--src/routes/(app)/pay/[username]/[...amount]/+page.server.ts8
-rw-r--r--src/routes/(app)/send/bitcoin/[address]/+page.svelte1
5 files changed, 598 insertions, 567 deletions
diff --git a/src/locales/en.json b/src/locales/en.json
index 486ebaee..fa3ad96d 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -1,540 +1,541 @@
{
- "incident": "NOTICE: Funds deposited before May 10 are temporarily locked while we attempt to recover from a data loss incident. We had to restore our database from a day-old backup so we need to ensure that balances and payment history are corrected to account for all activity that occurred during that period before we can release funds. We don't have an ETA at this time but we are working diligently to resolve the issue. We are sincerely sorry for the inconvenience.",
- "funds": {
- "anyone": "Anyone with the link",
- "addManager": "Add manager",
- "manage": "Manage",
- "withdrawAccess": "Withdraw access",
- "username": "Username",
- "returnToFund": "Back to fund"
- },
- "notes": {
- "contents": "Note contents",
- "notFound": "No notes found"
- },
- "accounts": {
- "qr": "QR",
- "edit": "Edit",
- "payments": "Payments",
- "notifications": "Notifications",
- "on": "On",
- "off": "Off",
- "maxfee": "Max routing fee",
- "maxfeeDesc": "The default is 1% of the payment value if this is not set",
- "per": "per",
- "none": "None",
- "day": "day",
- "week": "week",
- "month": "month",
- "year": "year",
- "daily": "Daily",
- "weekly": "Weekly",
- "monthly": "Monthly",
- "yearly": "Yearly",
- "never": "Never",
- "newConection": "New connection",
- "connect": "Connect",
- "name": "Name",
- "pubkey": "Pubkey",
- "secret": "Secret",
- "connection": "NWC Connection",
- "budget": "Budget",
- "delete": "Delete",
- "renewal": "Renewal period",
- "showQR": "Show QR",
- "revealNwc": "Reveal connection string",
- "finish": "Finish",
- "back": "Back",
- "next": "Next",
- "seedPhrase": "Seed phrase",
- "save": "Save",
- "copy": "Copy",
- "copyNwc": "Copy NWC",
- "revealMnemonic": "Reveal seed phrase",
- "regenerate": "Regenerate",
- "deleteAccount": "Delete account",
- "addAccount": "Create a non-custodial account",
- "create": "Create",
- "import": "Import",
- "accountSetup": "Account setup",
- "enterSeed": "Enter a seed phrase",
- "passwordMismatch": "Passwords don't match",
- "generateSeed": "Generate a new seed phrase",
- "accountPassword": "Account password",
- "importSeed": "Enter an existing seed phrase",
- "passwordWarning": "If you lose this password you won't be able to withdraw funds from the account or restore your seed phrase. We can't recover it for you.",
- "savings": "Savings",
- "password": "Password",
- "confirmPassword": "Confirm password"
- },
- "404": {
- "404": "404",
- "pageNotFound": "Page not found."
- },
- "about": {
- "header": "About",
- "team": {
- "header": "The Team",
- "adam": {
- "name": "Adam Soltys",
- "position": "Founder and CTO",
- "bio": "Adam is a software engineer who believes that the adoption of sound money will drive global prosperity. He lives in North Vancouver with his wife, daughter, and son."
- },
- "cole": {
- "name": "Cole",
- "position": "Business Development",
- "bio": "Cole is a driven individual with a passion for freedom, captivated by Bitcoin since 2015. His mission is to spread the adoption of Bitcoin and the Lightning Network across diverse communities globally."
- }
- },
- "locations": {
- "viewMap": "View map",
- "header": "Locations",
- "description": "Over 500 businesses worldwide are accepting bitcoin with Coinos. We hope that yours will be next!"
- }
- },
- "error": {
- "header": "Error",
- "message": "Please try again or contact support.",
- "signIn": "Please sign in to continue.",
- "unrecognizedInput": "Unrecognized input"
- },
- "faq": {
- "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."
- },
- "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."
- },
- "pos": {
- "question": "Can Coinos integrate with my existing PoS?",
- "answer": "We recommend entering sales into your existing PoS as cash transactions."
- },
- "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."
- }
- },
- "footer": {
- "company": "Company",
- "followUs": "Follow Us",
- "github": "GitHub",
- "linktree": "Linktree",
- "jobs": "Jobs",
- "support": "Support",
- "telegram": "Telegram",
- "twitter": "Twitter",
- "nostr": "Nostr",
- "documentation": "Documentation"
- },
- "howItWorks": {
- "header": "How it works",
- "step1": {
- "header": "1. Create your account",
- "description": "A username and password is all you need to get started."
- },
- "step2": {
- "header": "2. Ask for payments",
- "description": "The same as you normally would, but with bitcoin."
- },
- "step3": {
- "header": "3. That's all!",
- "description": "Spend your coin immediately or watch it grow in value."
- }
- },
- "invoice": {
- "addTip": "Add a tip",
- "addTipq": "Add a tip?",
- "addMemo": "Add memo",
- "setMemo": "Set memo",
- "memo": "Memo",
- "done": "Done",
- "paymentSuccessful": "Payment received!",
- "invoice": "Invoice",
- "total": "Total",
- "tip": "Tip"
- },
- "jobs": {
- "header": "Jobs",
- "subheader": "Current Openings",
- "pitch1": "Coinos is actively looking for developers to join our growing team (full-time or part-time, remote working OK).",
- "pitch2": "Excited about working on open source bitcoin? Then take a look at our repositories on",
- "github": "GitHub",
- "pitch3": "If you think you can make an impact, we'd love to hear from you. Send us your resume to"
- },
- "landing": {
- "header_before_icon": "The",
- "header_with_icon": "easiest",
- "header_after_icon": "way to get started with bitcoin.",
- "subheader": "A free web wallet and payment page for everyone.",
- "info1": {
- "title": "Instant payments via Lightning",
- "description": "We make it easy for merchants to receive retail payments. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% card fees."
- },
- "info2": {
- "title": "Simple to use",
- "description1": "Just enter an amount in your local currency, share the QR code and you’re done.",
- "description2": "We do all the heavy lifting so you can focus on your business.",
- "description3": "You always have the option of taking custody of your funds by withdrawing instantly at any time."
- },
- "info3": {
- "title": "Brand it your way!",
- "description": "Customize your profile page look and feel, or keep it on default - it's totally up to you."
- }
- },
- "login": {
- "forgot": "Forgot password?",
- "resetSent": "A password reset link will be sent to your email if we have it on file.",
- "continue": "Continue",
- "reset": "Password reset",
- "resetDesc": "We can send a reset link if you added and verified your email otherwise please reach out to",
- "optional": "Optional",
- "submit": "Submit",
- "token": "2FA Token",
- "username": "Username",
- "password": "Password",
- "newPassword": "New password",
- "email": "Email",
- "passwordRecommendation": "We recommend using a long, random password if you intend to keep funds in your account.",
- "forgotUserOrPassword": "Forgot password?",
- "haveAccount": "Already have an account?",
- "noAccount": "Don't have an account?",
- "register": "Register",
- "createAccount": "Create account",
- "signIn": "Sign In",
- "signUp": "Sign Up",
- "failed": "Login failed",
- "nostr": "Sign in with Nostr",
- "nostrConnect": "Nostr connect"
- },
- "nav": {
- "home": "Home",
- "dark": "Dark",
- "light": "Light",
- "about": "About",
- "docs": "Docs",
- "messages": "Messages",
- "funds": "Funds",
- "map": "Map",
- "merch": "Shop",
- "profile": "Profile",
- "register": "Register",
- "account": "Account",
- "startInSeconds": "Start in Seconds",
- "signIn": "Sign In",
- "signOut": "Sign out",
- "dashboard": "Dashboard",
- "settings": "Settings",
- "support": "Support"
- },
- "news": {
- "header": "News",
- "subheader": "Latest Updates"
- },
- "user": {
- "loadMore": "Load more",
- "url": "Public URL",
- "install": "Install app",
- "moreDetails": "Profile details",
- "noMessages": "You have not sent or received any messages",
- "profilePage": "Profile page",
- "nwc": "Nostr wallet connections",
- "nostrPubkey": "Nostr pubkey",
- "requestPayment": "Request Payment",
- "welcome": "Welcome! Your account balance is empty. You can start by making a deposit or by asking someone to pay you.",
- "showBalance": "Reveal balance",
- "bitcoin": "Bitcoin",
- "lightning": "Lightning",
- "invoice": "Invoice",
- "unfollow": "Unfollow",
- "following": "Following",
- "follow": "Follow",
- "followers": "Followers",
- "remove": "Remove",
- "noNotes": "No notes yet",
- "message": "Message",
- "messages": "Messages",
- "feed": "Feed",
- "notes": "Notes",
- "pay": "Pay",
- "scanToPayBTC": "Scan to pay with bitcoin",
- "this": "This",
- "lightningAddress": "Lightning address",
- "dashboard": {
- "setup": "Setup self-custody",
- "payNow": "Pay now",
- "checkout": "Checkout",
- "go": "Go",
- "dashboard": "Dashboard",
- "accountBalance": "Account Balance",
- "conversionFeeWarning": "You will be charged a 1% withdrawal fee on the amount.",
- "fetchingRate": "fetching rate...",
- "send": "Send",
- "receive": "Receive",
- "withdraw": "Send",
- "deposit": "Deposit",
- "voucher": "Voucher"
- },
- "receive": {
- "lessThan1BTCWarning": "Please enter an amount less than 1 bitcoin.",
- "showQR": "Show QR",
- "hideQR": "Hide QR"
- },
- "send": {
- "makeQR": "QR",
- "contacts": "Contacts",
- "scan": "Scan",
- "paste": "Paste",
- "next": "Next",
- "placeholder": "Enter an address, invoice or username"
- },
- "settings": {
- "nostr": "Nostr",
- "name": "Name",
- "integrations": "Integrations",
- "syncWithExtension": "Sync with extension",
- "connectNostr": "Nostr Wallet Connect",
- "pleaseEnterYourPassword": "Please enter your password",
- "memoPrompt": "Checkout instructions",
- "memoPromptPlaceholder": "e.g. Please enter your name for the order",
- "rememberFor": "Remember for",
- "import": "Import secret key",
- "destination": "Autowithdrawal address",
- "destinationDesc": "The address that withdrawals will be sent to",
- "destinationPlaceholder": "Enter a Bitcoin, Liquid or Lightning address",
- "reserve": "Reserve amount",
- "reserveDesc": "The amount to be left in your account after the withdrawal is complete",
- "shopify": "Shopify",
- "threshold": "Autowithdrawal threshold",
- "thresholdDesc": "When your balance exceeds this amount, a withdrawal will be triggered",
- "autoWithdraw": "Autowithdrawal",
- "autoWithdrawDescription": "Enable withdrawals to an external address when your balance reaches a given threshold",
- "notifications": "Email notifications",
- "pushNotifications": "Push notifications",
- "pushNotificationsDesc": "Get payment notifications on your device",
- "pushNotificationsDisabled": "You've denied notification permission on this device. Check your browser settings if you want to undo that.",
- "notificationsDesc": "Receive an email whenever a payment lands in your account",
- "seedPhrase": "Seed phrase",
- "nostrKeys": "Nostr secret key",
- "nostrDescription": "Your nostr secret key is used to sign and decrypt messages, keep it safe or someone may be able to impersonate you.",
- "nwc": "Nostr wallet connect (NWC)",
- "nwcDescription": "Enable nostr apps to send payments from your account",
- "seedDescription": "This 12 word seed phrase is used to derive your nostr private key and your self-custodial wallet keys which determine your auto-withdrawal addresses. Back it up and don't let anyone see it.",
- "emailDescription": "For password resets and payment notifications",
- "hideNsec": "Hide nostr secret key",
- "saved": "Settings saved!",
- "verifying": "Verification email sent",
- "verified": "Email verified!",
- "revealNsec": "Reveal secret key",
- "hideSeed": "Hide seed phrase",
- "revealSeed": "Reveal seed phrase",
- "saveSettings": "Save Settings",
- "bannerImage": "Banner Image",
- "profileImage": "Profile Image",
- "oneTimeCode": "Enter one-time code",
- "accountId": "Account Secret",
- "header": "Settings",
- "ACCOUNT": "PROFILE",
- "POINT_OF_SALE": "ACCOUNT",
- "NOSTR": "NOSTR",
- "SECURITY": "SECURITY",
- "locale": "Language",
- "username": "Username",
- "email": "Email",
- "newPassword": "Change Password",
- "about": "About",
- "aboutPlaceholder": "A little something about you or your business address and hours",
- "emailNotifications": "Email Notifications",
- "emailNotificationsDescription": "Get notified via email when a change occurs on your account or when people send you payments.",
- "smsNotifications": "SMS Notifications",
- "smsNotificationsDescription": "Coinos will send you text notifications when this is enabled.",
- "theme": "Color Theme",
- "tipPrompt": "Prompt for tips",
- "tipPromptDescription": "Ask people if they want to add a tip before paying their invoices",
- "displayName": "Display name",
- "themeDescription": "Select from the default color themes.",
- "localCurrency": "Currency",
- "btcUnit": "Display bitcoin in:",
- "bitcoin": "bitcoin",
- "seconds": "seconds",
- "minutes": "minutes",
- "hour": "hour",
- "hours": "hours",
- "satoshis": "Satoshis",
- "securityPIN": "Security PIN",
- "securityPINDescription": "Set a security pin that will be used to unlock your settings and to send outgoing payments.",
- "setPIN": "Set PIN",
- "autoLock": "Timeout",
- "autoLockDescription": "Time after which your PIN needs to be re-entered:",
- "twofa": "2-Factor Authentication",
- "twofaDescription": "Add an extra level of security to your device by enabling 2-factor authentication.",
- "twofaSetup": "Set up 2FA",
- "twofaDisable": "Disable 2FA",
- "select": "Select",
- "verifyPIN": "Verify PIN",
- "enablePIN": "Enable PIN",
- "disablePIN": "Disable PIN",
- "publishNip5": "Publish nostr identity",
- "nip5a": "Use",
- "nip5b": "as your publicly verified nostr address"
- },
- "support": {
- "fillForm": "Fill out this form or email us directly at",
- "doOurBest": "and we'll do our best to get back to you in a timely manner.",
- "thankYou": "Thank you!",
- "beInTouch": "Someone will be in touch shortly.",
- "header": "Support",
- "email": "Email",
- "accountName": "Account Name",
- "message": "Message",
- "send": "Send"
- }
- },
- "payments": {
- "lockedBalance": "Locked balance",
- "insufficientFunds": "Insufficient funds",
- "pending": "Pending",
- "minted": "Minted",
- "redeemed": "Redeemed",
- "exchangeRate": "Exchange rate",
- "viewFund": "View fund",
- "zappedEvent": "Zapped event",
- "accountSettings": "Account settings",
- "advancedSettings": "Advanced settings",
- "moreOptions": "More options",
- "other": "Other",
- "ok": "Ok",
- "cancel": "Cancel",
- "failedToRoute": "Failed to route payment, recipient may not be reachable. Try sending a smaller amount or increasing the max fee reserve.",
- "message": "Message",
- "enterWalletPass": "Wallet password",
- "legacyVersion": "Legacy version",
- "newVersion": "New version",
- "income": "Received",
- "subtotal": "Subtotal",
- "expenditure": "Spent",
- "spent": "Already spent",
- "mint": "Mint",
- "ecash": "Ecash",
- "memo": "Notes",
- "voucherText": "Enter an ecash token or lnurlw code",
- "createEcash": "Mint ecash",
- "lnurlw": "LNURLw",
- "tips": "Tips",
- "options": "options",
- "bump": "Bump fee",
- "networkFee": "Network fee",
- "platformFee": "Platform fee",
- "fastest": "Fastest",
- "fundingAmount": "Funding amount",
- "createFund": "Create a fund",
- "withdraw": "Withdraw",
- "fast": "Fast",
- "ecashDesc": "Ecash tokens are funded with bitcoin. They may be redeemed in exchange for a lightning payment equal to their value or loaded back into a coinos account at any time. They can also be transferred to other ecash wallets either wholly or partially. You might use them to give bitcoin to someone who doesn't have a wallet by texting the token to them or sharing a link to it. Learn more at",
- "ecashToken": "Ecash token",
- "medium": "Medium",
- "maxFeeDesc": "Set an upper limit on the network routing fee. If set too low the payment may fail. You can set it higher and try again. We debit the full amount while attempting the payment but credit back any unused portion.",
- "slow": "Slow",
- "create": "Create",
- "fund": "Fund",
- "addFunds": "Add funds",
- "takeFunds": "Take funds",
- "show": "Show",
- "showText": "Show text",
- "showQr": "Show QR",
- "hide": "Hide",
- "voucher": "Payment code",
- "qr": "QR",
- "copy": "Copy",
- "amountPrompt": "Prompt for amount",
- "shareLink": "Share",
- "copyLink": "Copy link",
- "copyText": "Copy text",
- "openLink": "Open",
- "setAmount": "Set amount",
- "redeemVoucher": "Ecash",
- "total": "Total",
- "gainLoss": "Gain/Loss",
- "presentValue": "Present Value",
- "amount": "Amount",
- "date": "Date",
- "id": "Transaction ID",
- "details": "Transaction Details",
- "header": "Payments",
- "preimage": "Lightning preimage",
- "maxfee": "Max routing fee",
- "type": "Type",
- "time": "Time",
- "sent": "Sent",
- "received": "Received",
- "empty": "No transactions yet.",
- "send": "Send",
- "next": "Next",
- "sendTo": "Send to",
- "to": "to",
- "for": "for",
- "pay": "Pay",
- "requesting": "is requesting",
- "sentInvoiceTo": "Sent invoice to",
- "sentRequestTo": "Request sent!",
- "pleasePay": "Please pay",
- "day": "Day",
- "week": "Week",
- "month": "Month",
- "all": "All",
- "export": "Export",
- "invoice": "Invoice",
- "readyToPay": "is ready to pay",
- "describeOrder": "Use this field to describe your order",
- "requestAnInvoice": "Request Invoice",
- "submit": "Submit",
- "isRequesting": "is requesting",
- "invoiceFrom": "Invoice from",
- "awaiting": "Awaiting",
- "tapAnywhere": "Tap anywhere to continue",
- "from": "from",
- "redeem": "Redeem",
- "swap": "Swap",
- "redeemedBy": "Redeemed by",
- "sendMessage": "Write a message...",
- "fundsClaimed": "Funds claimed!",
- "print": "Print",
- "onlyLbtc": "We only support",
- "dontDeposit": "Please don't deposit other Liquid assets"
- },
- "items": {
- "add": "Add item",
- "edit": "Edit item",
- "name": "Name",
- "price": "Price",
- "submit": "Submit",
- "image": "Image"
- },
- "lang": {
- "en": "English",
- "es": "Español",
- "fr": "Français",
- "de": "Deutsch",
- "ja": "日本語",
- "ru": "Русский",
- "pt": "Português",
- "zh": "中文",
- "fa": "فارسی ",
- "el": "Ελληνικά",
- "ar": "العربية",
- "hi": "हिन्दी",
- "bn": "বাংলা",
- "tr": "Türkçe",
- "ko": "한국어",
- "it": "Italiano",
- "nl": "Nederlands",
- "th": "ไทย",
- "pl": "Polski"
- }
+ "incident": "NOTICE: Funds deposited before May 10 are temporarily locked while we attempt to recover from a data loss incident. We had to restore our database from a day-old backup so we need to ensure that balances and payment history are corrected to account for all activity that occurred during that period before we can release funds. We don't have an ETA at this time but we are working diligently to resolve the issue. We are sincerely sorry for the inconvenience.",
+ "funds": {
+ "anyone": "Anyone with the link",
+ "addManager": "Add manager",
+ "manage": "Manage",
+ "withdrawAccess": "Withdraw access",
+ "username": "Username",
+ "returnToFund": "Back to fund"
+ },
+ "notes": {
+ "contents": "Note contents",
+ "notFound": "No notes found"
+ },
+ "accounts": {
+ "qr": "QR",
+ "edit": "Edit",
+ "payments": "Payments",
+ "notifications": "Notifications",
+ "on": "On",
+ "off": "Off",
+ "maxfee": "Max routing fee",
+ "maxfeeDesc": "The default is 1% of the payment value if this is not set",
+ "per": "per",
+ "none": "None",
+ "day": "day",
+ "week": "week",
+ "month": "month",
+ "year": "year",
+ "daily": "Daily",
+ "weekly": "Weekly",
+ "monthly": "Monthly",
+ "yearly": "Yearly",
+ "never": "Never",
+ "newConection": "New connection",
+ "connect": "Connect",
+ "name": "Name",
+ "pubkey": "Pubkey",
+ "secret": "Secret",
+ "connection": "NWC Connection",
+ "budget": "Budget",
+ "delete": "Delete",
+ "renewal": "Renewal period",
+ "showQR": "Show QR",
+ "revealNwc": "Reveal connection string",
+ "finish": "Finish",
+ "back": "Back",
+ "next": "Next",
+ "seedPhrase": "Seed phrase",
+ "save": "Save",
+ "copy": "Copy",
+ "copyNwc": "Copy NWC",
+ "revealMnemonic": "Reveal seed phrase",
+ "regenerate": "Regenerate",
+ "deleteAccount": "Delete account",
+ "addAccount": "Create a non-custodial account",
+ "create": "Create",
+ "import": "Import",
+ "accountSetup": "Account setup",
+ "enterSeed": "Enter a seed phrase",
+ "passwordMismatch": "Passwords don't match",
+ "generateSeed": "Generate a new seed phrase",
+ "accountPassword": "Account password",
+ "importSeed": "Enter an existing seed phrase",
+ "passwordWarning": "If you lose this password you won't be able to withdraw funds from the account or restore your seed phrase. We can't recover it for you.",
+ "savings": "Savings",
+ "password": "Password",
+ "confirmPassword": "Confirm password"
+ },
+ "404": {
+ "404": "404",
+ "pageNotFound": "Page not found."
+ },
+ "about": {
+ "header": "About",
+ "team": {
+ "header": "The Team",
+ "adam": {
+ "name": "Adam Soltys",
+ "position": "Founder and CTO",
+ "bio": "Adam is a software engineer who believes that the adoption of sound money will drive global prosperity. He lives in North Vancouver with his wife, daughter, and son."
+ },
+ "cole": {
+ "name": "Cole",
+ "position": "Business Development",
+ "bio": "Cole is a driven individual with a passion for freedom, captivated by Bitcoin since 2015. His mission is to spread the adoption of Bitcoin and the Lightning Network across diverse communities globally."
+ }
+ },
+ "locations": {
+ "viewMap": "View map",
+ "header": "Locations",
+ "description": "Over 500 businesses worldwide are accepting bitcoin with Coinos. We hope that yours will be next!"
+ }
+ },
+ "error": {
+ "header": "Error",
+ "message": "Please try again or contact support.",
+ "signIn": "Please sign in to continue.",
+ "unrecognizedInput": "Unrecognized input"
+ },
+ "faq": {
+ "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."
+ },
+ "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."
+ },
+ "pos": {
+ "question": "Can Coinos integrate with my existing PoS?",
+ "answer": "We recommend entering sales into your existing PoS as cash transactions."
+ },
+ "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."
+ }
+ },
+ "footer": {
+ "company": "Company",
+ "followUs": "Follow Us",
+ "github": "GitHub",
+ "linktree": "Linktree",
+ "jobs": "Jobs",
+ "support": "Support",
+ "telegram": "Telegram",
+ "twitter": "Twitter",
+ "nostr": "Nostr",
+ "documentation": "Documentation"
+ },
+ "howItWorks": {
+ "header": "How it works",
+ "step1": {
+ "header": "1. Create your account",
+ "description": "A username and password is all you need to get started."
+ },
+ "step2": {
+ "header": "2. Ask for payments",
+ "description": "The same as you normally would, but with bitcoin."
+ },
+ "step3": {
+ "header": "3. That's all!",
+ "description": "Spend your coin immediately or watch it grow in value."
+ }
+ },
+ "invoice": {
+ "addTip": "Add a tip",
+ "addTipq": "Add a tip?",
+ "addMemo": "Add memo",
+ "setMemo": "Set memo",
+ "memo": "Memo",
+ "done": "Done",
+ "paymentSuccessful": "Payment received!",
+ "invoice": "Invoice",
+ "total": "Total",
+ "tip": "Tip"
+ },
+ "jobs": {
+ "header": "Jobs",
+ "subheader": "Current Openings",
+ "pitch1": "Coinos is actively looking for developers to join our growing team (full-time or part-time, remote working OK).",
+ "pitch2": "Excited about working on open source bitcoin? Then take a look at our repositories on",
+ "github": "GitHub",
+ "pitch3": "If you think you can make an impact, we'd love to hear from you. Send us your resume to"
+ },
+ "landing": {
+ "header_before_icon": "The",
+ "header_with_icon": "easiest",
+ "header_after_icon": "way to get started with bitcoin.",
+ "subheader": "A free web wallet and payment page for everyone.",
+ "info1": {
+ "title": "Instant payments via Lightning",
+ "description": "We make it easy for merchants to receive retail payments. No waiting for bank settlements or worrying about chargebacks. Instant settlement, in seconds. Say goodbye to 3% card fees."
+ },
+ "info2": {
+ "title": "Simple to use",
+ "description1": "Just enter an amount in your local currency, share the QR code and you’re done.",
+ "description2": "We do all the heavy lifting so you can focus on your business.",
+ "description3": "You always have the option of taking custody of your funds by withdrawing instantly at any time."
+ },
+ "info3": {
+ "title": "Brand it your way!",
+ "description": "Customize your profile page look and feel, or keep it on default - it's totally up to you."
+ }
+ },
+ "login": {
+ "forgot": "Forgot password?",
+ "resetSent": "A password reset link will be sent to your email if we have it on file.",
+ "continue": "Continue",
+ "reset": "Password reset",
+ "resetDesc": "We can send a reset link if you added and verified your email otherwise please reach out to",
+ "optional": "Optional",
+ "submit": "Submit",
+ "token": "2FA Token",
+ "username": "Username",
+ "password": "Password",
+ "newPassword": "New password",
+ "email": "Email",
+ "passwordRecommendation": "We recommend using a long, random password if you intend to keep funds in your account.",
+ "forgotUserOrPassword": "Forgot password?",
+ "haveAccount": "Already have an account?",
+ "noAccount": "Don't have an account?",
+ "register": "Register",
+ "createAccount": "Create account",
+ "signIn": "Sign In",
+ "signUp": "Sign Up",
+ "failed": "Login failed",
+ "nostr": "Sign in with Nostr",
+ "nostrConnect": "Nostr connect"
+ },
+ "nav": {
+ "home": "Home",
+ "dark": "Dark",
+ "light": "Light",
+ "about": "About",
+ "docs": "Docs",
+ "messages": "Messages",
+ "funds": "Funds",
+ "map": "Map",
+ "merch": "Shop",
+ "profile": "Profile",
+ "register": "Register",
+ "account": "Account",
+ "startInSeconds": "Start in Seconds",
+ "signIn": "Sign In",
+ "signOut": "Sign out",
+ "dashboard": "Dashboard",
+ "settings": "Settings",
+ "support": "Support"
+ },
+ "news": {
+ "header": "News",
+ "subheader": "Latest Updates"
+ },
+ "user": {
+ "loadMore": "Load more",
+ "url": "Public URL",
+ "lnurl": "Reusable payment code",
+ "install": "Install app",
+ "moreDetails": "Profile details",
+ "noMessages": "You have not sent or received any messages",
+ "profilePage": "Profile page",
+ "nwc": "Nostr wallet connections",
+ "nostrPubkey": "Nostr pubkey",
+ "requestPayment": "Request Payment",
+ "welcome": "Welcome! Your account balance is empty. You can start by making a deposit or by asking someone to pay you.",
+ "showBalance": "Reveal balance",
+ "bitcoin": "Bitcoin",
+ "lightning": "Lightning",
+ "invoice": "Invoice",
+ "unfollow": "Unfollow",
+ "following": "Following",
+ "follow": "Follow",
+ "followers": "Followers",
+ "remove": "Remove",
+ "noNotes": "No notes yet",
+ "message": "Message",
+ "messages": "Messages",
+ "feed": "Feed",
+ "notes": "Notes",
+ "pay": "Pay",
+ "scanToPayBTC": "Scan to pay with bitcoin",
+ "this": "This",
+ "lightningAddress": "Lightning address",
+ "dashboard": {
+ "setup": "Setup self-custody",
+ "payNow": "Pay now",
+ "checkout": "Checkout",
+ "go": "Go",
+ "dashboard": "Dashboard",
+ "accountBalance": "Account Balance",
+ "conversionFeeWarning": "You will be charged a 1% withdrawal fee on the amount.",
+ "fetchingRate": "fetching rate...",
+ "send": "Send",
+ "receive": "Receive",
+ "withdraw": "Send",
+ "deposit": "Deposit",
+ "voucher": "Voucher"
+ },
+ "receive": {
+ "lessThan1BTCWarning": "Please enter an amount less than 1 bitcoin.",
+ "showQR": "Show QR",
+ "hideQR": "Hide QR"
+ },
+ "send": {
+ "makeQR": "QR",
+ "contacts": "Contacts",
+ "scan": "Scan",
+ "paste": "Paste",
+ "next": "Next",
+ "placeholder": "Enter an address, invoice or username"
+ },
+ "settings": {
+ "nostr": "Nostr",
+ "name": "Name",
+ "integrations": "Integrations",
+ "syncWithExtension": "Sync with extension",
+ "connectNostr": "Nostr Wallet Connect",
+ "pleaseEnterYourPassword": "Please enter your password",
+ "memoPrompt": "Checkout instructions",
+ "memoPromptPlaceholder": "e.g. Please enter your name for the order",
+ "rememberFor": "Remember for",
+ "import": "Import secret key",
+ "destination": "Autowithdrawal address",
+ "destinationDesc": "The address that withdrawals will be sent to",
+ "destinationPlaceholder": "Enter a Bitcoin, Liquid or Lightning address",
+ "reserve": "Reserve amount",
+ "reserveDesc": "The amount to be left in your account after the withdrawal is complete",
+ "shopify": "Shopify",
+ "threshold": "Autowithdrawal threshold",
+ "thresholdDesc": "When your balance exceeds this amount, a withdrawal will be triggered",
+ "autoWithdraw": "Autowithdrawal",
+ "autoWithdrawDescription": "Enable withdrawals to an external address when your balance reaches a given threshold",
+ "notifications": "Email notifications",
+ "pushNotifications": "Push notifications",
+ "pushNotificationsDesc": "Get payment notifications on your device",
+ "pushNotificationsDisabled": "You've denied notification permission on this device. Check your browser settings if you want to undo that.",
+ "notificationsDesc": "Receive an email whenever a payment lands in your account",
+ "seedPhrase": "Seed phrase",
+ "nostrKeys": "Nostr secret key",
+ "nostrDescription": "Your nostr secret key is used to sign and decrypt messages, keep it safe or someone may be able to impersonate you.",
+ "nwc": "Nostr wallet connect (NWC)",
+ "nwcDescription": "Enable nostr apps to send payments from your account",
+ "seedDescription": "This 12 word seed phrase is used to derive your nostr private key and your self-custodial wallet keys which determine your auto-withdrawal addresses. Back it up and don't let anyone see it.",
+ "emailDescription": "For password resets and payment notifications",
+ "hideNsec": "Hide nostr secret key",
+ "saved": "Settings saved!",
+ "verifying": "Verification email sent",
+ "verified": "Email verified!",
+ "revealNsec": "Reveal secret key",
+ "hideSeed": "Hide seed phrase",
+ "revealSeed": "Reveal seed phrase",
+ "saveSettings": "Save Settings",
+ "bannerImage": "Banner Image",
+ "profileImage": "Profile Image",
+ "oneTimeCode": "Enter one-time code",
+ "accountId": "Account Secret",
+ "header": "Settings",
+ "ACCOUNT": "PROFILE",
+ "POINT_OF_SALE": "ACCOUNT",
+ "NOSTR": "NOSTR",
+ "SECURITY": "SECURITY",
+ "locale": "Language",
+ "username": "Username",
+ "email": "Email",
+ "newPassword": "Change Password",
+ "about": "About",
+ "aboutPlaceholder": "A little something about you or your business address and hours",
+ "emailNotifications": "Email Notifications",
+ "emailNotificationsDescription": "Get notified via email when a change occurs on your account or when people send you payments.",
+ "smsNotifications": "SMS Notifications",
+ "smsNotificationsDescription": "Coinos will send you text notifications when this is enabled.",
+ "theme": "Color Theme",
+ "tipPrompt": "Prompt for tips",
+ "tipPromptDescription": "Ask people if they want to add a tip before paying their invoices",
+ "displayName": "Display name",
+ "themeDescription": "Select from the default color themes.",
+ "localCurrency": "Currency",
+ "btcUnit": "Display bitcoin in:",
+ "bitcoin": "bitcoin",
+ "seconds": "seconds",
+ "minutes": "minutes",
+ "hour": "hour",
+ "hours": "hours",
+ "satoshis": "Satoshis",
+ "securityPIN": "Security PIN",
+ "securityPINDescription": "Set a security pin that will be used to unlock your settings and to send outgoing payments.",
+ "setPIN": "Set PIN",
+ "autoLock": "Timeout",
+ "autoLockDescription": "Time after which your PIN needs to be re-entered:",
+ "twofa": "2-Factor Authentication",
+ "twofaDescription": "Add an extra level of security to your device by enabling 2-factor authentication.",
+ "twofaSetup": "Set up 2FA",
+ "twofaDisable": "Disable 2FA",
+ "select": "Select",
+ "verifyPIN": "Verify PIN",
+ "enablePIN": "Enable PIN",
+ "disablePIN": "Disable PIN",
+ "publishNip5": "Publish nostr identity",
+ "nip5a": "Use",
+ "nip5b": "as your publicly verified nostr address"
+ },
+ "support": {
+ "fillForm": "Fill out this form or email us directly at",
+ "doOurBest": "and we'll do our best to get back to you in a timely manner.",
+ "thankYou": "Thank you!",
+ "beInTouch": "Someone will be in touch shortly.",
+ "header": "Support",
+ "email": "Email",
+ "accountName": "Account Name",
+ "message": "Message",
+ "send": "Send"
+ }
+ },
+ "payments": {
+ "lockedBalance": "Locked balance",
+ "insufficientFunds": "Insufficient funds",
+ "pending": "Pending",
+ "minted": "Minted",
+ "redeemed": "Redeemed",
+ "exchangeRate": "Exchange rate",
+ "viewFund": "View fund",
+ "zappedEvent": "Zapped event",
+ "accountSettings": "Account settings",
+ "advancedSettings": "Advanced settings",
+ "moreOptions": "More options",
+ "other": "Other",
+ "ok": "Ok",
+ "cancel": "Cancel",
+ "failedToRoute": "Failed to route payment, recipient may not be reachable. Try sending a smaller amount or increasing the max fee reserve.",
+ "message": "Message",
+ "enterWalletPass": "Wallet password",
+ "legacyVersion": "Legacy version",
+ "newVersion": "New version",
+ "income": "Received",
+ "subtotal": "Subtotal",
+ "expenditure": "Spent",
+ "spent": "Already spent",
+ "mint": "Mint",
+ "ecash": "Ecash",
+ "memo": "Notes",
+ "voucherText": "Enter an ecash token or lnurlw code",
+ "createEcash": "Mint ecash",
+ "lnurlw": "LNURLw",
+ "tips": "Tips",
+ "options": "options",
+ "bump": "Bump fee",
+ "networkFee": "Network fee",
+ "platformFee": "Platform fee",
+ "fastest": "Fastest",
+ "fundingAmount": "Funding amount",
+ "createFund": "Create a fund",
+ "withdraw": "Withdraw",
+ "fast": "Fast",
+ "ecashDesc": "Ecash tokens are funded with bitcoin. They may be redeemed in exchange for a lightning payment equal to their value or loaded back into a coinos account at any time. They can also be transferred to other ecash wallets either wholly or partially. You might use them to give bitcoin to someone who doesn't have a wallet by texting the token to them or sharing a link to it. Learn more at",
+ "ecashToken": "Ecash token",
+ "medium": "Medium",
+ "maxFeeDesc": "Set an upper limit on the network routing fee. If set too low the payment may fail. You can set it higher and try again. We debit the full amount while attempting the payment but credit back any unused portion.",
+ "slow": "Slow",
+ "create": "Create",
+ "fund": "Fund",
+ "addFunds": "Add funds",
+ "takeFunds": "Take funds",
+ "show": "Show",
+ "showText": "Show text",
+ "showQr": "Show QR",
+ "hide": "Hide",
+ "voucher": "Payment code",
+ "qr": "QR",
+ "copy": "Copy",
+ "amountPrompt": "Prompt for amount",
+ "shareLink": "Share",
+ "copyLink": "Copy link",
+ "copyText": "Copy text",
+ "openLink": "Open",
+ "setAmount": "Set amount",
+ "redeemVoucher": "Ecash",
+ "total": "Total",
+ "gainLoss": "Gain/Loss",
+ "presentValue": "Present Value",
+ "amount": "Amount",
+ "date": "Date",
+ "id": "Transaction ID",
+ "details": "Transaction Details",
+ "header": "Payments",
+ "preimage": "Lightning preimage",
+ "maxfee": "Max routing fee",
+ "type": "Type",
+ "time": "Time",
+ "sent": "Sent",
+ "received": "Received",
+ "empty": "No transactions yet.",
+ "send": "Send",
+ "next": "Next",
+ "sendTo": "Send to",
+ "to": "to",
+ "for": "for",
+ "pay": "Pay",
+ "requesting": "is requesting",
+ "sentInvoiceTo": "Sent invoice to",
+ "sentRequestTo": "Request sent!",
+ "pleasePay": "Please pay",
+ "day": "Day",
+ "week": "Week",
+ "month": "Month",
+ "all": "All",
+ "export": "Export",
+ "invoice": "Invoice",
+ "readyToPay": "is ready to pay",
+ "describeOrder": "Use this field to describe your order",
+ "requestAnInvoice": "Request Invoice",
+ "submit": "Submit",
+ "isRequesting": "is requesting",
+ "invoiceFrom": "Invoice from",
+ "awaiting": "Awaiting",
+ "tapAnywhere": "Tap anywhere to continue",
+ "from": "from",
+ "redeem": "Redeem",
+ "swap": "Swap",
+ "redeemedBy": "Redeemed by",
+ "sendMessage": "Write a message...",
+ "fundsClaimed": "Funds claimed!",
+ "print": "Print",
+ "onlyLbtc": "We only support",
+ "dontDeposit": "Please don't deposit other Liquid assets"
+ },
+ "items": {
+ "add": "Add item",
+ "edit": "Edit item",
+ "name": "Name",
+ "price": "Price",
+ "submit": "Submit",
+ "image": "Image"
+ },
+ "lang": {
+ "en": "English",
+ "es": "Español",
+ "fr": "Français",
+ "de": "Deutsch",
+ "ja": "日本語",
+ "ru": "Русский",
+ "pt": "Português",
+ "zh": "中文",
+ "fa": "فارسی ",
+ "el": "Ελληνικά",
+ "ar": "العربية",
+ "hi": "हिन्दी",
+ "bn": "বাংলা",
+ "tr": "Türkçe",
+ "ko": "한국어",
+ "it": "Italiano",
+ "nl": "Nederlands",
+ "th": "ไทย",
+ "pl": "Polski"
+ }
}
diff --git a/src/routes/(app)/[username]/(profile)/+layout.svelte b/src/routes/(app)/[username]/(profile)/+layout.svelte
index afd94ab0..f3e018ee 100644
--- a/src/routes/(app)/[username]/(profile)/+layout.svelte
+++ b/src/routes/(app)/[username]/(profile)/+layout.svelte
@@ -16,12 +16,13 @@
} from "$lib/utils";
import { t } from "$lib/translations";
import { sign, send } from "$lib/nostr";
- import { bech32m } from "@scure/base";
+ import { bech32 } from "@scure/base";
import { page } from "$app/stores";
+ import { PUBLIC_DOMAIN } from "$env/static/public";
let { data, children } = $props();
- let { encode, toWords } = bech32m;
+ let { encode, toWords } = bech32;
let {
events,
@@ -107,6 +108,17 @@
: `${stripped}@${$page.url.host}`,
);
let profile = $derived(`${$page.url.host}/${subject.anon ? npub : stripped}`);
+ let lnurl = $derived(
+ encode(
+ "lnurl",
+ toWords(
+ new TextEncoder().encode(
+ `https://${PUBLIC_DOMAIN}/p/${subject.anon ? npub : stripped}`,
+ ),
+ ),
+ 20000,
+ ),
+ );
$effect(() => followList.then((l) => (list = l)));
let following = $derived(list.some((t) => t.includes(subject.pubkey)));
</script>
@@ -214,18 +226,20 @@
</div>
</div>
<div>
- <div class="text-secondary">{$t("user.nostrPubkey")}</div>
+ <div class="text-secondary">{$t("user.lnurl")}</div>
<div class="flex gap-4">
<div class="break-all grow text-xl">
- {subject.pubkey}
+ {lnurl}
</div>
- <div class="flex my-auto gap-1">
- <button class="my-auto" onclick={() => copy(subject.pubkey)}
+ <div class="flex mb-auto gap-1">
+ <button class="my-auto" onclick={() => copy(lnurl)}
><iconify-icon noobserver icon="ph:copy-bold" width="32"
></iconify-icon></button
>
<a
- href={`/qr/${encodeURIComponent(subject.pubkey)}`}
+ href={`/qr/${encodeURIComponent(
+ `${$page.url.protocol}//${lnurl}`,
+ )}`}
class="my-auto"
>
<iconify-icon noobserver icon="ph:qr-code-bold" width="32"
diff --git a/src/routes/(app)/ln/[lnurl]/+page.server.ts b/src/routes/(app)/ln/[lnurl]/+page.server.ts
index 405dc4b3..1ee182b1 100644
--- a/src/routes/(app)/ln/[lnurl]/+page.server.ts
+++ b/src/routes/(app)/ln/[lnurl]/+page.server.ts
@@ -1,6 +1,9 @@
+import { PUBLIC_DOMAIN } from "$env/static/public";
import getRates from "$lib/rates";
-import { auth, fd, get, post } from "$lib/utils";
+import { auth, fd, get, post, types } from "$lib/utils";
+import { bech32 } from "@scure/base";
import { error, fail, redirect } from "@sveltejs/kit";
+const { decode, fromWords } = bech32;
export async function load({ params, parent }) {
const { user } = await parent();
@@ -8,28 +11,44 @@ export async function load({ params, parent }) {
const rates = await getRates();
let data;
- try {
- const { lnurl } = params;
- data = await get(`/decode?text=${lnurl}`);
- let { callback, minSendable, maxSendable, comment, tag } = data;
- if (tag === "payRequest" && minSendable === maxSendable) {
- minSendable = Math.round(minSendable / 1000);
- maxSendable = Math.round(maxSendable / 1000);
- const amount = minSendable;
+ const { lnurl } = params;
- let url = `${callback}?amount=${amount * 1000}`;
- if (comment) url += `&comment=${comment}`;
+ const url = Buffer.from(fromWords(decode(lnurl, 20000).words)).toString();
- const { pr } = await fetch(url).then((r) => r.json());
- let path = `/send/lightning/${pr}`;
- if (comment) path += `/${encodeURIComponent(comment)}`;
- redirect(307, path);
- }
+ try {
+ data = await get(`/decode?text=${lnurl}`);
} catch (e) {
const { message } = e as Error;
error(500, message);
}
+ let { callback, minSendable, maxSendable, comment, tag } = data;
+ if (callback.includes(PUBLIC_DOMAIN)) {
+ const username = url.split(`https://${PUBLIC_DOMAIN}/p/`)[1];
+ // const inv = await post("/invoice", {
+ // invoice: { type: types.lightning, amount: minSendable / 1000 },
+ // user: { username },
+ // });
+ // const { id: iid } = inv;
+ let redir = `/pay/${username}`;
+ if (minSendable === maxSendable && minSendable > 0) redir += `/${minSendable / 1000}`;
+ redirect(307, redir);
+ }
+ if (tag === "payRequest" && minSendable === maxSendable) {
+ minSendable = Math.round(minSendable / 1000);
+ maxSendable = Math.round(maxSendable / 1000);
+ const amount = minSendable;
+
+ let url = `${callback}?amount=${amount * 1000}`;
+ if (comment) url += `&comment=${comment}`;
+
+ const { pr } = await fetch(url).then((r) => (r as Response).json());
+ console.log("PR", pr);
+ let path = `/send/lightning/${pr}`;
+ if (comment) path += `/${encodeURIComponent(comment)}`;
+ redirect(307, path);
+ }
+
if (!["payRequest", "withdrawRequest"].includes(data.tag))
error(500, "We only support LNURLp and LNURLw at this time");
diff --git a/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts b/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts
index b0f824ed..9c2b1e3b 100644
--- a/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts
+++ b/src/routes/(app)/pay/[username]/[...amount]/+page.server.ts
@@ -13,7 +13,7 @@ export async function load({ cookies, params, parent }) {
let [amount, currency] = params.amount.split("/");
const rate = rates[currency ? currency.toUpperCase() : subject.currency];
- if (!rate) error(500, "Invalid currency symbol");
+ if (currency && !rate) error(500, "Invalid currency symbol");
if (amount) {
if (currency) amount = (amount * sats) / rate;
@@ -33,8 +33,6 @@ export async function load({ cookies, params, parent }) {
auth(cookies),
);
- const { balance } = await get(`/account/${user.id}`, auth(cookies));
-
redirect(307, `/invoice/${id}`);
}
@@ -66,8 +64,8 @@ export const actions = {
return fail(400, { message });
}
- if (parseInt(amount) > 0 && prompt) redirect(307, `/invoice/${id}/tip`);
- else if (parseInt(amount) <= 0 ) redirect(307, `/${username}/receive`);
+ if (parseInt(amount) > 0 && prompt) redirect(307, `/invoice/${id}/tip`);
+ else if (parseInt(amount) <= 0) redirect(307, `/${username}/receive`);
redirect(307, `/invoice/${id}`);
},
};
diff --git a/src/routes/(app)/send/bitcoin/[address]/+page.svelte b/src/routes/(app)/send/bitcoin/[address]/+page.svelte
index 154534a7..671de954 100644
--- a/src/routes/(app)/send/bitcoin/[address]/+page.svelte
+++ b/src/routes/(app)/send/bitcoin/[address]/+page.svelte
@@ -34,7 +34,6 @@
<div class="text-xl text-secondary break-all">{address}</div>
- <div>{amount}</div>
<Numpad bind:amount={a} bind:fiat {currency} {submit} bind:rate={$rate} />
<div class="flex justify-center gap-2">