mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
I18n automation (#77)
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
# App specific
|
||||
.env
|
||||
.vscode/
|
||||
google-services.json
|
||||
# OSX
|
||||
#
|
||||
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"inlang.vs-code-extension"
|
||||
]
|
||||
}
|
||||
Vendored
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"[javascript][typescript][javascriptreact][typescriptreact]": {
|
||||
"editor.insertSpaces": true,
|
||||
"editor.tabSize": 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
const { exec } = require('child_process');
|
||||
|
||||
exec('tsc --noEmit', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
const output = stdout;
|
||||
const filteredErrors = output.split('\n').filter(line => line.includes('TxKeyPath'));
|
||||
if (filteredErrors.length > 0) {
|
||||
console.log(filteredErrors.join('\n'));
|
||||
} else {
|
||||
console.log('No errors containing TxKeyPath found.');
|
||||
}
|
||||
// process.exit(1); // Ensure the script returns an error code if there were any errors
|
||||
} else {
|
||||
console.log('No TypeScript errors found.');
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"$schema": "https://inlang.com/schema/project-settings",
|
||||
"sourceLanguageTag": "en",
|
||||
"languageTags": [
|
||||
"en",
|
||||
"sk"
|
||||
],
|
||||
"modules": [
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-empty-pattern@latest/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/message-lint-rule-without-source@latest/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/gh/KraXen72/inlang-t-function-jsx-hybrid-matcher/dist/index.js",
|
||||
"https://cdn.jsdelivr.net/npm/@inlang/plugin-json@latest/dist/index.js"
|
||||
],
|
||||
"plugin.inlang.json": {
|
||||
"pathPattern": "./src/i18n_messages/{languageTag}.json",
|
||||
"variableReferencePattern": [
|
||||
"%{",
|
||||
"}"
|
||||
]
|
||||
},
|
||||
"plugin.minibits.inlangmatcher": {
|
||||
"preferredTfuncName": "translate",
|
||||
"recognizedTfuncNames": [
|
||||
"translate"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@
|
||||
"start": "npx react-native start",
|
||||
"test": "jest",
|
||||
"adb": "adb devices && adb reverse tcp:9090 tcp:9090 && adb reverse tcp:3000 tcp:3000 && adb reverse tcp:9001 tcp:9001 && adb reverse tcp:8081 tcp:8081",
|
||||
"i18n:missing": "node __tests__/missingTranslations.js",
|
||||
"postinstall": "patch-package --exclude 'nothing'"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
-325
@@ -1,325 +0,0 @@
|
||||
const en = {
|
||||
common: {
|
||||
ok: "OK",
|
||||
cancel: "Cancel",
|
||||
close: "Close",
|
||||
back: "Back",
|
||||
paste: "Paste",
|
||||
scan: "Scan",
|
||||
copy: "Copy",
|
||||
copyFailParam: "Couldn't copy %{param}",
|
||||
confirm: "Confirm",
|
||||
save: "Save",
|
||||
add: "Add",
|
||||
success: "Success!",
|
||||
offline: "Offline",
|
||||
qr: "QR Code",
|
||||
sats: "SATS",
|
||||
notCreated: 'Not yet created',
|
||||
resetDefault: 'Reset to default',
|
||||
share: "Share",
|
||||
},
|
||||
paymentCommon: {
|
||||
pay: "Pay",
|
||||
payMe: 'Pay me',
|
||||
payFromWallet: 'Pay from wallet',
|
||||
receiveInPerson: "Receive in person"
|
||||
},
|
||||
transactionCommon: {
|
||||
from: "From %{sender}",
|
||||
receivedFrom: "Received from %{sender}",
|
||||
sentTo: "Sent to %{receiver}",
|
||||
paidTo: "Paid to %{receiver}",
|
||||
youReceived: 'You received',
|
||||
youSent: 'You sent',
|
||||
youPaid: 'You paid',
|
||||
unknown: 'Unknown transaction',
|
||||
status: {
|
||||
completedFee: 'Completed %{fee}',
|
||||
draft: 'Draft',
|
||||
error: 'Error',
|
||||
pending: 'Pending',
|
||||
prepared: 'Prepared',
|
||||
reverted: 'Reverted',
|
||||
blocked: 'Blocked',
|
||||
expired: 'Expired'
|
||||
},
|
||||
tapToRedeem: 'Tap to redeem',
|
||||
redeemOnline: 'Redeem online',
|
||||
feePaid: 'Paid fee'
|
||||
},
|
||||
welcomeScreen: {
|
||||
page1: {
|
||||
heading: "Welcome",
|
||||
intro: "Minibits is an ecash and Lightning wallet with a focus on performance and usability. Ecash is a bearer token issued by custodians known as mints.",
|
||||
bullet1: "Minibits follows the Cashu protocol, where mints back ecash with Bitcoin.",
|
||||
bullet2: "Ecash is issued or exchanged back to Bitcoin instantly through Lightning payments.",
|
||||
bullet3: "Ecash tokens are stored on-device; mints do not keep ledger or wallet balances.",
|
||||
final: "No Watson, there is no blockchain."
|
||||
},
|
||||
page2: {
|
||||
heading: "Why Minibits?",
|
||||
intro: "Minibits' aim is to research how Lightning and ecash can be integrated into a seamless and instant experience that can work at scale and still provide good privacy.",
|
||||
bullet1: "Minibits provides sharable wallet identifiers using NOSTR addresses. Like account numbers, just better.",
|
||||
bullet2: "Try in-person sends while devices are offline - ecash settles when back online (Proof of concept).",
|
||||
bullet3: "You can opt-in for storage encryption and biometric authentication.",
|
||||
final: "Minibits is free and open-source software; find us on Github for the roadmap and contributions."
|
||||
},
|
||||
page3: {
|
||||
heading: "Do not forget",
|
||||
intro: "Both the Cashu protocol and the Minibits wallet are still experimental, and by using them, you accept known and unknown risks.",
|
||||
bullet1: "Mints are, by design, custodial services. Run your own or use them only for research and testing purposes.",
|
||||
bullet2: "Ecash is stored on your device, so the loss of the device means the loss of ecash. Remote backup protocol is still under research.",
|
||||
bullet3: "Minibits provides its own mint that you can use for testing purposes with small amounts. It is operated on a best-effort basis and without any guarantees.",
|
||||
final: "Now, let's move some ecash!",
|
||||
go: "Let's go!"
|
||||
}
|
||||
},
|
||||
walletScreen: {
|
||||
fund: "Fund wallet",
|
||||
withdraw: "Withdraw",
|
||||
send: "Send",
|
||||
receive: "Receive",
|
||||
topUpWallet: "Top up wallet",
|
||||
topUpWalletSubText: "Top up your balance by paying Bitcoin lightning invoice from another wallet",
|
||||
transferFromWallet: "Transfer from wallet",
|
||||
transferFromWalletSubText: "Debit your balance by paying Bitcoin lightning invoice from this wallet",
|
||||
feeBadge: {
|
||||
final: "+ final fee %{fee} %{code}",
|
||||
upto: "+ fee up to %{fee} %{code}"
|
||||
}
|
||||
},
|
||||
receiveScreen: {
|
||||
title: "Receive",
|
||||
paste: "Paste ecash",
|
||||
receive: "Receive",
|
||||
receiveOffline: "Receive offline",
|
||||
newMintsAdded: {
|
||||
one: "{{count}} mint has been added to your wallet",
|
||||
other: "{{count}} mints have been added to your wallet",
|
||||
},
|
||||
toReceive: "Amount to receive",
|
||||
received: "Amount received",
|
||||
sharePaymentRequest: "Send payment request",
|
||||
sharePaymentRequestDescription: "Send payment request to one of your contacts, so that the payer can pay you exact amount.",
|
||||
scanToReceive: "Scan or paste to receive",
|
||||
scanToReceiveDescription: "Scan or paste ecash token or lnurl withdraw link to receive into your wallet.",
|
||||
pasteToReceive: "Paste",
|
||||
pasteoReceiveDescription: "Paste ecash token to receive it into your wallet.",
|
||||
showOrShareInvoice: "Share lightning invoice",
|
||||
showOrShareInvoiceDescription: "Present or share lightning invoice for an amount you want to receive." ,
|
||||
scanWithdrawalLink: "Scan withdrawal link"
|
||||
},
|
||||
transferScreen: {
|
||||
|
||||
},
|
||||
topupScreen: {
|
||||
|
||||
},
|
||||
sendScreen: {
|
||||
title: "Send",
|
||||
sendToContact: "Send to contact",
|
||||
sendToContactDescription: "Send your ecash to one of your contacts stored in your contact list.",
|
||||
scanToPay: "Scan to pay",
|
||||
scanToPayDescription: "Scan Lightning invoice, LNURL pay code or Lightning address to pay.",
|
||||
pasteToSend: "Paste",
|
||||
pasteToSendDescription: "Paste lightning invoice from clipboard to pay it from your wallet.",
|
||||
showOrShareToken: "Share ecash",
|
||||
showOrShareTokenDescription: "Present or share ecash token for an amount you want to send.",
|
||||
},
|
||||
tranDetailScreen: {
|
||||
amount: "Amount",
|
||||
lightningFee: "Lightning network fee",
|
||||
memoFromSender: "Memo from sender",
|
||||
sentFrom: "Sent from",
|
||||
sentTo: "Sent to",
|
||||
type: "Type",
|
||||
status: "Status",
|
||||
balanceAfter: "Balance after this transaction",
|
||||
createdAt: "Created at",
|
||||
id: "ID",
|
||||
memoToReceiver: "Memo to receiver",
|
||||
revert: "Revert",
|
||||
claim: "Claim",
|
||||
memoFromInvoice: "Memo from invoice",
|
||||
paidFrom: "Paid from",
|
||||
receivedTo: "Received to",
|
||||
topupTo: "Topup to",
|
||||
trasferredTo: "Paid to",
|
||||
invoice: "Lightning invoice to pay",
|
||||
receiveOfflineComplete: "Redeem to wallet",
|
||||
isOffline: "Redeem online"
|
||||
},
|
||||
contactsScreen: {
|
||||
new: "New",
|
||||
scan: "Scan",
|
||||
newTitle: "Add new contact",
|
||||
getInvoice: "Get invoice",
|
||||
ownName: {
|
||||
donationSuccess: "Thank you! Donation for %{receiver} has been successfully paid.",
|
||||
tooShort: 'Write your wallet profile name to the text box, use min 2 characters.',
|
||||
illegalChar: 'Do not use . or - characters at the beginning or the end of name.',
|
||||
profileExists: 'This wallet name is already in use, choose another one.',
|
||||
chooseOwnName: 'Choose your own name',
|
||||
chooseOwnNameFooter: 'Use lowercase letters, numbers and .-_',
|
||||
available: "%{name} is available!",
|
||||
payToGetOwnName: "Pay the following lightning invoice and get your %{name} wallet name.",
|
||||
ctaPay: "Pay from wallet",
|
||||
insufficient: 'Your wallet balance is not enough to pay this invoice amount but you can still pay it from another wallet.',
|
||||
betaWarning: 'Please accept this is an early beta software. Your data can still be lost due to a bug or unexpected data loss.'
|
||||
},
|
||||
privateContacts: {
|
||||
selectSendPaymentRequest: 'Select contact to send your payment request to.',
|
||||
selectSendToken: 'Select contact to send your ecash to.',
|
||||
selectSendLnURL: 'Select contact to send Lightning payment to.',
|
||||
saveNewFormat: "Please enter a wallet address in name@domain.xyz format",
|
||||
profileNotFound: "Profile name %{name} could not be found. Make sure the name is correct.",
|
||||
noLightningAddress: 'This contact does not have a Lightning address, send ecash instead.',
|
||||
explainerText: "Private contacts",
|
||||
explainerSubText: "Add other Minibits users as your private contacts. Every user gets sharable @minibits.cash wallet address. You can pay privately to your contacts anytime even if they are offline.",
|
||||
switchName: "Switch your wallet name and picture?",
|
||||
switchNameSubText: 'Get cooler wallet name or profile picture. Select from an array of random names and images or opt for your own @minibits.cash wallet name.',
|
||||
bottomModal: "Private contacts are unique identifiers of other Minibits wallets. You can use them to send or request ecash and you can safely share your own with others.",
|
||||
domainMinibits: "Use minibits.cash domain",
|
||||
domainExternal: "Use another domain"
|
||||
},
|
||||
publicContacts: {
|
||||
npubPasteError: 'Copy your NPUB key first, then paste',
|
||||
relayurlPasteError: 'Copy your relay URL key first, then paste',
|
||||
relayExists: 'Relay already exists.',
|
||||
missingLightningAddress: 'This contact does not have a Lightning address, send ecash instead.',
|
||||
nostrTip: 'Tip the people you follow',
|
||||
nostrTipSubText: 'Add your NOSTR social network public key (npub) and tip or donate to your favourite people and projects directly from the minibits wallet.',
|
||||
nostrSetPublicKey: 'Set your public key',
|
||||
nostrSetPublicKeySubText: 'Add or change your NOSTR social network public key (npub).',
|
||||
nostrSetRelay: 'Set relay',
|
||||
nostrSetRelaySubText: 'Add or change your own relay if your profile and follows are not hosted on the default relays.',
|
||||
nostrRemovePub: 'Remove your public key',
|
||||
nostrRemovePubSubText: 'Remove your npub key and stop loading public contacts.',
|
||||
addNpub: 'Add your npub key',
|
||||
pasteDemoKey: 'Paste demo key',
|
||||
setOwnRelay: 'Set your own relay'
|
||||
},
|
||||
randomName: {
|
||||
selectOneOfUsernames: 'Select one of the usernames'
|
||||
}
|
||||
},
|
||||
paymentRequestScreen: {
|
||||
incoming: {
|
||||
noRequests: 'There are no incoming payment requests to be paid or they have already expired.'
|
||||
},
|
||||
outgoing: {
|
||||
noRequests: 'There are no outgoing payment requests to be paid or they have already expired.',
|
||||
invoiceShared: 'Lightning invoice has been shared, waiting to be paid by receiver.',
|
||||
sharingCancelled: 'Sharing cancelled',
|
||||
scanAndPay: 'Scan and pay to top-up'
|
||||
},
|
||||
// might move this to common later if it gets used in other screens
|
||||
listItem: {
|
||||
expires: "Expires",
|
||||
expired: "Expired",
|
||||
requestPaymentSuccess: 'Request has been paid'
|
||||
}
|
||||
},
|
||||
|
||||
profileScreen: {
|
||||
changeAvatar: "Change picture",
|
||||
changeAvatarSubtext: "Select one from randomly generated pictures.",
|
||||
changeWalletaddress: "Change wallet address",
|
||||
changeWalletaddressSubtext: "Change unique wallet address. Select one of randomly generated or choose your own.",
|
||||
},
|
||||
settingsScreen: {
|
||||
title: "Settings",
|
||||
manageMints: "Manage mints",
|
||||
preferredUnit: "Preferred unit",
|
||||
mintsCount: {
|
||||
one: "{{count}} mint",
|
||||
other: "{{count}} mints",
|
||||
},
|
||||
go: "Let's go!",
|
||||
backupRecovery: "Backup and recovery",
|
||||
security: "Security",
|
||||
privacy: "Privacy",
|
||||
devOptions: "Developer options",
|
||||
update: "Update manager"
|
||||
},
|
||||
mintsScreen: {
|
||||
addMintUrl: "Add mint URL",
|
||||
mintUrl: "Mint URL",
|
||||
mintUrlHint: "Provide the URL of a trusted mint",
|
||||
invalidUrl: "This is not a valid URL",
|
||||
mintExists: "This mint already exists",
|
||||
addMint: "Add mint",
|
||||
mintAdded: "The mint has been added",
|
||||
mintInfo: "Mint information page",
|
||||
rename: "Rename mint",
|
||||
setColor: "Change the mint's icon color",
|
||||
copy: "Copy mint URL",
|
||||
removeMint: "Remove mint",
|
||||
blockMint: "Block receiving from this mint",
|
||||
unblockMint: "Unblock receiving from this mint",
|
||||
mintRemoved: "The mint has been removed",
|
||||
mintBlocked: "The mint has been blocked. The app will not receive ecash from this mint.",
|
||||
mintUnblocked: "The mint has been unblocked. You can now receive ecash from this mint."
|
||||
},
|
||||
backupScreen: {
|
||||
localBackup: "Local backup",
|
||||
localBackupDescription: "Local backup stores a copy of all your ecash in the local database. Ecash is never deleted unless you switch off the backup. Please note that this backup is not encrypted.",
|
||||
remoteBackup: "Seed backup",
|
||||
remoteBackupDescription: "Mnemonic phrase encodes your seed and allows you to recover your ecash balance in case of device loss.",
|
||||
recoveryTool: "Local recovery tool",
|
||||
recoveryToolDescription: "Show ecash backed up in the local database and attempt to recover unspent ones into the wallet in case the wallet storage gets corrupted or a transaction fails due to an unexpected error.",
|
||||
removeSpentCoins: "Remove spent ecash",
|
||||
removeSpentCoinsDescription: "In case of a SEND or TRANSFER transaction error, spent ecash may remain in your wallet. This blocks further transactions with the mint, making the wallet unusable. This tool removes spent ecash from the wallet."
|
||||
},
|
||||
securityScreen: {
|
||||
encryptStorage: "Encrypt storage",
|
||||
encryptStorageDescription: "Encrypt the storage that stores your ecash with the secret key generated on your device. Experimental, not recommended for every day use.",
|
||||
biometry: "Biometric authentication",
|
||||
biometryAvailable: "Biometric authentication is now required for Minibits to start and unlock the encryption key.",
|
||||
biometryNone: "You have not setup biometric authentication or your device does not support it.",
|
||||
},
|
||||
privacyScreen: {
|
||||
torDaemon: "Use Tor network",
|
||||
torDaemonDescription: "Allow connections with mints through Tor .onion addresses.",
|
||||
torStatus: "Tor status",
|
||||
logger: "Enable logging",
|
||||
loggerDescription: "Log anonymous error information in order to identify and fix wallet bugs during beta testing. Switch off for best privacy.",
|
||||
},
|
||||
updateScreen: {
|
||||
updateAvailable: "Update is available",
|
||||
nativeUpdateAvailableDesc: "There is a new version of Minibits Wallet in the Google Play Store.",
|
||||
gotoPlayStore: 'Visit Play Store',
|
||||
updateNow: 'Update and restart',
|
||||
updateAvailableDesc: "There is a new Over-the-Air update of Minibits Wallet ready to download and install.",
|
||||
updateNew: "What's new?",
|
||||
updateSize: "Download size",
|
||||
updateNotAvailable: 'Nothing to update',
|
||||
updateNotAvailableDesc: 'Good, you are running the latest version of the app.'
|
||||
},
|
||||
developerScreen: {
|
||||
title: "Developer options",
|
||||
transactions: "Sync recent transactions",
|
||||
transactionsDescription: "There are {{count}} recent transactions cached in the app's fast storage. Resetting will reload them from the app's database. Do only during development or testing.",
|
||||
reset: "Factory reset",
|
||||
resetDescription: "This will wipe out all local data from storage and from the local database. Do only during development or testing.",
|
||||
go: "Let's go!",
|
||||
devOptions: "Developer options",
|
||||
info: "About Minibits wallet",
|
||||
logLevel: "Log level"
|
||||
},
|
||||
errorScreen: {
|
||||
title: "Something went wrong!",
|
||||
friendlySubtitle:
|
||||
"Unfortunately, the Minibits app encountered an unexpected error. Please contact us at support@minibits.cash or raise an issue on Github.",
|
||||
reset: "RESET APP",
|
||||
},
|
||||
tabNavigator: {
|
||||
walletLabel: "Wallet",
|
||||
contactsLabel: "Contacts",
|
||||
settingsLabel: "Settings",
|
||||
},
|
||||
};
|
||||
|
||||
export default en;
|
||||
export type Translations = typeof en;
|
||||
+3
-4
@@ -1,12 +1,11 @@
|
||||
// import * as Localization from "expo-localization"
|
||||
// import { I18nManager } from "react-native"
|
||||
import { I18n } from "i18n-js"
|
||||
import { I18nManager } from "react-native"
|
||||
|
||||
// if English isn't your default language, move Translations to the appropriate language file.
|
||||
import en, { Translations } from "./en"
|
||||
|
||||
|
||||
import en from "../i18n_messages/en.json"
|
||||
|
||||
type Translations = typeof en;
|
||||
export const i18n = new I18n({'en-US': en})
|
||||
|
||||
// i18n.fallbacks = true
|
||||
|
||||
@@ -12,7 +12,7 @@ import {TxKeyPath} from './i18n'
|
||||
* @example
|
||||
* Translations:
|
||||
* ```ts
|
||||
* // en.ts
|
||||
* // en.json
|
||||
* {
|
||||
* "heading": "Welcome to the app!",
|
||||
* "buttonText": "Donate",
|
||||
|
||||
@@ -0,0 +1,612 @@
|
||||
{
|
||||
"addMints": "Add mints",
|
||||
"amount": {
|
||||
"requested": "Requested amount",
|
||||
"topup": "Topup amount",
|
||||
"withdraw": "Withdraw amount"
|
||||
},
|
||||
"attention": "Attention!",
|
||||
"backupScreen": {
|
||||
"deletedSuccess": "Your backup of minibits tokens has been deleted. New tokens will NOT be backed up.",
|
||||
"invalidMnemonicError": "Corrupted or invalid mnemonic. Please move funds and reinstall this wallet.",
|
||||
"localBackup": "Local backup",
|
||||
"localBackupDescription": "Local backup stores a copy of all your ecash in the local database. Ecash is never deleted unless you switch off the backup. Please note that this backup is not encrypted.",
|
||||
"missingMnemonicError": "Missing mnemonic phrase.",
|
||||
"missingMnemonicOrSeedError": "Missing mnemonic or seed.",
|
||||
"mnemonicDesc": "This 12 word sequence represents your seed and allows you to recover your ecash balance in case of device loss. Keep them in safe place.",
|
||||
"mnemonicTitle": "Your mnemonic phrase",
|
||||
"recoveryTool": "Local recovery tool",
|
||||
"recoveryToolDescription": "Show ecash backed up in the local database and attempt to recover unspent ones into the wallet in case the wallet storage gets corrupted or a transaction fails due to an unexpected error.",
|
||||
"remoteBackupDescription": "Mnemonic phrase encodes your seed and allows you to recover your ecash balance in case of device loss.",
|
||||
"removeSpentCoins": "Remove spent ecash",
|
||||
"removeSpentCoinsDescription": "In case of a SEND or TRANSFER transaction error, spent ecash may remain in your wallet. This blocks further transactions with the mint, making the wallet unusable. This tool removes spent ecash from the wallet.",
|
||||
"result": "%{proofCount} ecash proofs, %{amount} in total were removed from the wallet.",
|
||||
"seedBackup": "Seed backup",
|
||||
"success": "Your minibits tokens were backed up to local database. New tokens will be backed up automatically.",
|
||||
"remoteBackup": "Seed backup"
|
||||
},
|
||||
"backupWillOverwriteBalanceWarning": "Your wallet has non zero-balance. If you continue, existing ecash will be deleted and replaced by backup.",
|
||||
"checkAndSync": "Check and sync",
|
||||
"checkAndSyncDesc": "Checks that contact name is still linked to the same pubkey and updates picture if it was changed.",
|
||||
"common": {
|
||||
"add": "Add",
|
||||
"back": "Back",
|
||||
"cancel": "Cancel",
|
||||
"change": "Change",
|
||||
"close": "Close",
|
||||
"complete": "Complete",
|
||||
"confirm": "Confirm",
|
||||
"confirmAlertTitle": "Confirmation",
|
||||
"confirmCreateInvoice": "Create invoice",
|
||||
"connectedParam": "Connected: %{param}",
|
||||
"copy": "Copy",
|
||||
"copyFailParam": "Couldn't copy %{param}",
|
||||
"create": "Create",
|
||||
"disabled": "Disabled",
|
||||
"enabled": "Enabled",
|
||||
"hideMore": "Collapse",
|
||||
"import": "Import",
|
||||
"noDescPlaceholder": "No description",
|
||||
"notCreated": "Not yet created",
|
||||
"off": "off",
|
||||
"offline": "Offline",
|
||||
"offlinePretty": "Your device is currently offline",
|
||||
"ok": "OK",
|
||||
"on": "on",
|
||||
"paste": "Paste",
|
||||
"pasteEcash": "Paste ecash",
|
||||
"pasteEcashToken": "Paste ecash token",
|
||||
"qr": "QR Code",
|
||||
"reconnect": "Reconnect",
|
||||
"reset": "Reset",
|
||||
"resetDefault": "Reset to default",
|
||||
"sats": "SATS",
|
||||
"save": "Save",
|
||||
"saveFailParam": "Failed to save %{param}",
|
||||
"scan": "Scan",
|
||||
"sendAsEcash": "Send as ecash",
|
||||
"share": "Share",
|
||||
"showMore": "Show more",
|
||||
"success": "Success!"
|
||||
},
|
||||
"confirmBackupRecovery": "Do you really want to recover ecash from the backup?",
|
||||
"confirmMintRemoval": "Do you really want to remove %{hostname} - %{shortname} from the wallet?",
|
||||
"contacts": "Contacts",
|
||||
"contactsScreen": {
|
||||
"getInvoice": "Get invoice",
|
||||
"new": "New",
|
||||
"newTitle": "Add new contact",
|
||||
"ownName": {
|
||||
"available": "%{name} is available!",
|
||||
"betaWarning": "Please accept this is an early beta software. Your data can still be lost due to a bug or unexpected data loss.",
|
||||
"chooseOwnName": "Choose your own name",
|
||||
"chooseOwnNameFooter": "Use lowercase letters, numbers and .-_",
|
||||
"ctaPay": "Pay from wallet",
|
||||
"donationSuccess": "Thank you! Donation for %{receiver} has been successfully paid.",
|
||||
"illegalChar": "Do not use . or - characters at the beginning or the end of name.",
|
||||
"insufficient": "Your wallet balance is not enough to pay this invoice amount but you can still pay it from another wallet.",
|
||||
"payToGetOwnName": "Pay the following lightning invoice and get your %{name} wallet name.",
|
||||
"profileExists": "This wallet name is already in use, choose another one.",
|
||||
"tooShort": "Please use at least 2 characters for your name"
|
||||
},
|
||||
"privateContacts": {
|
||||
"bottomModal": "Private contacts are unique identifiers of other Minibits wallets. You can use them to send or request ecash and you can safely share your own with others.",
|
||||
"domainExternal": "Use another domain",
|
||||
"domainMinibits": "Use minibits.cash domain",
|
||||
"explainerSubText": "Add other Minibits users as your private contacts. Every user gets sharable @minibits.cash wallet address. You can pay privately to your contacts anytime even if they are offline.",
|
||||
"explainerText": "Private contacts",
|
||||
"profileNotFound": "Profile name %{name} could not be found. Make sure the name is correct.",
|
||||
"saveNewFormat": "Please enter a wallet address in name@domain.xyz format",
|
||||
"selectSendLnURL": "Select contact to send Lightning payment to.",
|
||||
"selectSendPaymentRequest": "Select contact to send your payment request to.",
|
||||
"selectSendToken": "Select contact to send your ecash to.",
|
||||
"switchName": "Switch your wallet name and picture?",
|
||||
"switchNameSubText": "Get cooler wallet name or profile picture. Select from an array of random names and images or opt for your own @minibits.cash wallet name."
|
||||
},
|
||||
"publicContacts": {
|
||||
"addNpub": "Add your npub key",
|
||||
"npubPasteError": "Copy your NPUB key before pasting",
|
||||
"pasteDemoKey": "Paste demo key",
|
||||
"setOwnRelay": "Set your own relay"
|
||||
},
|
||||
"randomName": {
|
||||
"selectOneOfUsernames": "Select one of the usernames"
|
||||
},
|
||||
"scan": "Scan"
|
||||
},
|
||||
"copyAsEncodedTokens": "Copy as encoded tokens",
|
||||
"copyContactPublicKey": "Copy contact's public key",
|
||||
"copyMnemonicBackupWorkaround": "To apply backup to your existing funds, send your entire balance to yourself. Otherwise, only funds from this point onward can be restored.",
|
||||
"copyProofs": "text=\"Copy proofs\"",
|
||||
"decodedMissingCurrencyUnit": "Currency unit is missing in the received token. Wallet will assume token amount is in bitcoin $%{unit}. Do not continue if you are not sure this is correct.",
|
||||
"deleteContact": "Delete contact",
|
||||
"deleteContactDesc": "Remove this contact from your wallet.",
|
||||
"derivingSeedStatus": "Deriving seed, this takes a while...",
|
||||
"developerScreen": {
|
||||
"devOptions": "Developer options",
|
||||
"go": "Let's go!",
|
||||
"info": "About Minibits wallet",
|
||||
"logLevel": "Log level",
|
||||
"reset": "Factory reset",
|
||||
"resetDescription": "This will wipe out all local data from storage and from the local database. Do only during development or testing.",
|
||||
"title": "Developer options",
|
||||
"transactions": "Sync recent transactions",
|
||||
"transactionsDescription": "There are {{count}} recent transactions cached in the app's fast storage. Resetting will reload them from the app's database. Do only during development or testing."
|
||||
},
|
||||
"errorScreen": {
|
||||
"friendlySubtitle": "Unfortunately, the Minibits app encountered an unexpected error. Please contact us at support@minibits.cash or raise an issue on Github.",
|
||||
"reset": "RESET APP",
|
||||
"title": "Something went wrong!"
|
||||
},
|
||||
"factoryResetSuccess": "Factory reset completed, please restart immediately.",
|
||||
"factoryResetUserConfirmDesc": "All application data will be deleted. Are you sure you want to perform a factory reset?",
|
||||
"increaseRecoveryIndexes": "Increase recovery indexes",
|
||||
"increaseRecoveryIndexesDesc": "After incomplete recovery from seed or other rare issue, you may encounter 'duplicate outputs' error when trying to send. This resets your recovery indexes to higher value in order to resolve the issue.",
|
||||
"invalidRelayUrl": "Invalid relay URL.",
|
||||
"lightningAddress": "Lightning address",
|
||||
"lightningInvoiceSharedWaiting": "Lightning invoice has been shared, waiting to be paid by receiver.",
|
||||
"lightningPayScreen": {
|
||||
"onPasteEmptyClipboard": "First copy Lightning invoice, address or pay code to pay to. Then paste."
|
||||
},
|
||||
"loadingPublicInfo": "Loading public info",
|
||||
"localBackupDisabled": "Local backup is off",
|
||||
"localBackupEnabled": "Local backup is on",
|
||||
"loglevelDebugDesc": "Log details, set only if testing or on request by support.",
|
||||
"loglevelErrorDesc": "Log errors and crashes only, without private data.",
|
||||
"loglevelInfoDesc": "Log anonymous usage, without private data.",
|
||||
"manageMints": "Manage mints",
|
||||
"mintInfoHeading": "Mint info",
|
||||
"mintSettingsUpdated": "Updated mint settings",
|
||||
"mintsScreen": {
|
||||
"addMint": "Add mint",
|
||||
"addMintMinibits": "Add Minibits mint",
|
||||
"addMintUrl": "Add mint URL",
|
||||
"blockMint": "Block receiving from this mint",
|
||||
"copy": "Copy mint URL",
|
||||
"invalidUrl": "This is not a valid URL",
|
||||
"mintAdded": "The mint has been added",
|
||||
"mintBlocked": "The mint has been blocked. The app will not receive ecash from this mint.",
|
||||
"mintExists": "This mint already exists",
|
||||
"mintInfo": "Mint information page",
|
||||
"mintRemoved": "The mint has been removed",
|
||||
"mintUnblocked": "The mint has been unblocked. You can now receive ecash from this mint.",
|
||||
"mintUrl": "Mint URL",
|
||||
"mintUrlHint": "Provide the URL of a trusted mint",
|
||||
"refreshMintSettings": "Refresh mint settings",
|
||||
"removeMint": "Remove mint",
|
||||
"rename": "Rename mint",
|
||||
"setColor": "Change the mint's icon color",
|
||||
"unblockMint": "Unblock receiving from this mint"
|
||||
},
|
||||
"missingEcashTokenToReceiveError": "Missing ecash token to receive.",
|
||||
"missingLNURLParamsError": "Missing LNURL params.",
|
||||
"missingMintsForProofsUserMessage": "Please add all mints belonging to your proofs to the wallet and try again.",
|
||||
"missingMintUnitRouteParamsError": "Missing mint unit in route params",
|
||||
"mnemonicPhrasePlaceholder": "Mnemonic phrase...",
|
||||
"newMint": "New mint",
|
||||
"nextInterval": "Next interval",
|
||||
"noEcashRecovered": "No ecash recovered",
|
||||
"noSpentEcashFound": "No spent ecash found in your wallet",
|
||||
"nostr": {
|
||||
"copyPubKey": "Copy Nostr public key",
|
||||
"enterAddress": "Enter your Nostr address",
|
||||
"enterAddressDesc": "Minibits uses Nostr address (nip05) as a sharable contact to send and receive ecash.",
|
||||
"invalidAddressFormat": "Invalid NOSTR address, please check that it follows name@domain.com format",
|
||||
"lastStandDialog": {
|
||||
"cancel": "Cancel",
|
||||
"complete": "Profile change is complete.",
|
||||
"confirm": "Apply Change",
|
||||
"desc": "Using your own Nostr address will disable Lightning address features unique to minibits.cash address.",
|
||||
"readyDesc": "Wallet needs to restart to apply this change.",
|
||||
"readyTitle": "Profile change is ready",
|
||||
"title": "Consider before change"
|
||||
},
|
||||
"minibitsNameKeyReuseError": "%{domain} names and keys can't be used with multiple wallets.",
|
||||
"missingRelaysError": "Missing NOSTR relays",
|
||||
"pasteError": "Copy your NOSTR address first, then paste.",
|
||||
"profileRelayMismatchedIdentifierError": "Profile from the relay does not match the given nip05 identifier",
|
||||
"relaysTitle": "Nostr relays",
|
||||
"removePub": "Remove your public key",
|
||||
"removePubSubText": "Remove your npub key and stop loading public contacts.",
|
||||
"setPublicKey": "Set your public key",
|
||||
"setPublicKeySubText": "Add or change your NOSTR social network public key (npub).",
|
||||
"setRelay": "Set relay",
|
||||
"setRelaySubText": "Add or change your own relay if your profile and follows are not hosted on the default relays.",
|
||||
"tip": "Tip the people you follow",
|
||||
"tipSubText": "Add your NOSTR social network public key (npub) and tip or donate to your favourite people and projects directly from the minibits wallet.",
|
||||
"useOwnProfile": {
|
||||
"desc": "Import your own Nostr address and keys. Your wallet will stop communicating with minibits.cash Nostr and LNURL address servers, disabling Lightning address features for receiving zaps and payments. Only for hardcore ecash-ers!",
|
||||
"title": "Use own Nostr profile"
|
||||
}
|
||||
},
|
||||
"nsecEnterPrivateKey": "Enter your private key",
|
||||
"nsecEnterPrivateKeyDesc": "Minibits needs your private key in nsec format in order to decrypt messages containing incoming payments. Your key will be stored in your device secure key vault.",
|
||||
"nsecMissingProfileError": "Missing profile to update",
|
||||
"nsecPasteError": "Copy your nsec key first, then paste.",
|
||||
"nsecPrivatePublicKeyMismatchError": "Provided private key does not match your new profile public key.",
|
||||
"numberOfBackedupProofs": "Number of backed up proofs",
|
||||
"onDeviceInfo": "On device information",
|
||||
"pasteEcashTokenDesc": "Paste ecash token you want to receive.",
|
||||
"payCommon": {
|
||||
"amountToPayLabel": "Amount to pay",
|
||||
"amountZeroOrNegative": "Amount should be positive number.",
|
||||
"completed": "Payment completed",
|
||||
"failed": "Payment failed",
|
||||
"insufficientFunds": "There are not enough funds to send this amount.",
|
||||
"isPending": "Payment is pending",
|
||||
"maximumPay": "Maximum amount to pay is %{amount} %{currency}.",
|
||||
"minimumWithdraw": "Minimal withdraw amount is %{amount} %{currency}.",
|
||||
"pay": "Pay",
|
||||
"payFrom": "Pay from",
|
||||
"payFromWallet": "Pay from wallet",
|
||||
"payMe": "Pay me",
|
||||
"paymentRequests": "Payment requests",
|
||||
"payNow": "Pay now",
|
||||
"payToAddress": "Pay to this address",
|
||||
"payWithLightning": "Pay with Lightning",
|
||||
"receive": "Receive",
|
||||
"receiveEcash": "Receive ecash",
|
||||
"receiveInPerson": "Receive in person",
|
||||
"receiveOffline": "Receive offline",
|
||||
"requestPayment": "Request payment",
|
||||
"send": "Send",
|
||||
"withdraw": "Withdraw"
|
||||
},
|
||||
"payerMemo": "Memo for the payer",
|
||||
"paymentRequestScreen": {
|
||||
"incoming": {
|
||||
"noRequests": "There are no incoming payment requests to be paid or they have already expired."
|
||||
},
|
||||
"listItem": {
|
||||
"expired": "Expired",
|
||||
"expires": "Expires",
|
||||
"requestPaymentSuccess": "Request has been paid"
|
||||
},
|
||||
"outgoing": {
|
||||
"noRequests": "There are no outgoing payment requests to be paid or they have already expired.",
|
||||
"scanAndPay": "Scan and pay to top-up"
|
||||
}
|
||||
},
|
||||
"payWithLightningDesc": "Enter or paste Lightning address, invoice or LNURL pay code you want to pay to.",
|
||||
"pictureRetrieveFail": "Could not retrieve pictures from the server, try again later.",
|
||||
"privacyScreen": {
|
||||
"logger": "Enable logging",
|
||||
"loggerDescription": "Log anonymous error information in order to identify and fix wallet bugs during beta testing. Switch off for best privacy.",
|
||||
"torDaemon": "Use Tor network",
|
||||
"torDaemonDescription": "Allow connections with mints through Tor .onion addresses.",
|
||||
"torStatus": "Tor status"
|
||||
},
|
||||
"privateKey": "Private key",
|
||||
"privateNotePlaceholder": "Your private note",
|
||||
"profileMissingAddressError": "Missing address",
|
||||
"profileMissingNostrAddress": "This profile does not have nostr address to send ecash to.",
|
||||
"profileOnboarding": {
|
||||
"desc": "Your minibits.cash wallet address allows you to receive encrypted ecash over Nostr. Simultaneously, it serves as your Lightning address, enabling you to receive payments from any Lightning wallet or zaps on the Nostr social network.",
|
||||
"minibitsDesc": "Share your wallet address to receive encrypted ecash over Nostr. This also serves as your Lightning address, enabling you to receive payments from any Lightning wallet or zaps on the Nostr social network.",
|
||||
"minibitsTitle": "Your Minibits wallet address",
|
||||
"ownAddrDesc": "You are using your own Nostr address to send and receive ecash. Please note that this setup does not allow you to receive Nostr zaps or Lightning payments to this address.",
|
||||
"ownAddrTitle": "Your own wallet address",
|
||||
"title": "Create wallet address"
|
||||
},
|
||||
"profilePublicKeyMismatchError": "Profile from relays public key differs from yor wallet pubkey. Reset this profile in Privacy settings and import again with new keys.",
|
||||
"profileScreen": {
|
||||
"changeAvatar": "Change picture",
|
||||
"changeAvatarSubtext": "Select one from randomly generated pictures.",
|
||||
"changeWalletaddress": "Change wallet address",
|
||||
"changeWalletaddressSubtext": "Change unique wallet address. Select one of randomly generated or choose your own."
|
||||
},
|
||||
"profileToRecoverDesc": "This is the wallet address linked to your seed. If you press Complete, your wallet profile will reset to above seed and address.",
|
||||
"pushNotifications": "Push notifications",
|
||||
"qr": {
|
||||
"fail": "Could not display QR code, copy or share the Ecash token instead."
|
||||
},
|
||||
"receiverMemo": "Memo for the receiver",
|
||||
"receiveScreen": {
|
||||
"memoFromSender": "Memo from sender",
|
||||
"newMintsAdded": {
|
||||
"one": "{{count}} mint has been added to your wallet",
|
||||
"other": "{{count}} mints have been added to your wallet"
|
||||
},
|
||||
"pasteoReceiveDescription": "Paste ecash token to receive it into your wallet.",
|
||||
"pasteToReceive": "Paste",
|
||||
"received": "Amount received",
|
||||
"scanToReceive": "Scan or paste to receive",
|
||||
"scanToReceiveDescription": "Scan or paste ecash token or LNURL withdraw link to receive into your wallet.",
|
||||
"scanWithdrawalLink": "Scan withdrawal link",
|
||||
"sharePaymentRequest": "Send payment request",
|
||||
"sharePaymentRequestDescription": "Send payment request to one of your contacts, so that the payer can pay you exact amount.",
|
||||
"showOrShareInvoice": "Share lightning invoice",
|
||||
"showOrShareInvoiceDescription": "Present or share lightning invoice for an amount you want to receive.",
|
||||
"status": "Status",
|
||||
"title": "Receive",
|
||||
"toReceive": "Amount to receive"
|
||||
},
|
||||
"recoverToWallet": "Recover to wallet",
|
||||
"recovery": {
|
||||
"completeCTA": "Complete recovery",
|
||||
"completed": "Recovery completed",
|
||||
"completing": "Completing recovery...",
|
||||
"failed": "Recovery failed",
|
||||
"findAddressCTA": "Find wallet address",
|
||||
"foundAddrParam": "Found %{addr}",
|
||||
"foundPendingProofsAmount": "Found %{amount} pending proofs...",
|
||||
"foundProofsAmount": "Found %{amount} proofs...",
|
||||
"intervalParam": "Recovery interval %{startIndex} - %{endIndex} · ",
|
||||
"keysetID": "Keyset ID %{id} (%{unit})",
|
||||
"missingMnemonicSeedProfileError": "Missing mnemonic or seed or profile to recover.",
|
||||
"mnemonicCollision": "Mnemonic exists",
|
||||
"mnemonicCollisionDesc": "Your wallet already has another mnemonic in its secure storage. Recovery process works only with freshly installed wallet to avoid loss of your funds.",
|
||||
"noKeysetSelected": "Please select keyset.",
|
||||
"noMintSelected": "Please select the mint.",
|
||||
"noWalletForSeedError": "Could not find wallet address linked to your seed on the server.",
|
||||
"ownKeysImportAgain": "You used wallet address %{addr} with your own keys, import it again.",
|
||||
"recoveredResult": "%{formattedCurrency} %{code} were recovered into your wallet.",
|
||||
"recoveringAddress": "Recovering wallet address...",
|
||||
"restoringFromParam": "Restoring from %{hostname}...",
|
||||
"resultErrors": "Recovery ended with errors.",
|
||||
"resultExpired": "Nothing has been found in this recovery interval.",
|
||||
"resultSpent": "Good news is that already spent Ecash has been found. Continue with next recovery interval.",
|
||||
"seedFromCurrentDeviceError": "Can not use seed from this device.",
|
||||
"selectAnotherKeyset": " · Select another",
|
||||
"selectKeyset": "Select keyset to be used for recovery",
|
||||
"selectMintFrom": "Select mint to recover from.",
|
||||
"setManually": "Set manually",
|
||||
"spentProofsAmount": "%{amount} proofs were already spent...",
|
||||
"startIndexDesc": "Use to increase starting index of recovery interval e.g. in case of repeated recovery. You need to know at what index you completed your previous recovery or you'll miss ecash to recover.",
|
||||
"starting": "Starting recovery...",
|
||||
"success": "Recovery success!"
|
||||
},
|
||||
"recoveryFromMints": "Recovery from mints",
|
||||
"recoveryFromMintsDesc": "Identify mints to recover your ecash from and add them to the list.",
|
||||
"recoveryIndexesIncSuccess": "Recovery indexes increased by %{indCount}",
|
||||
"recoveryInsertMnemonic": "Insert mnemonic phrase",
|
||||
"recoveryInsertMnemonicDesc": "Paste or rewrite 12 word phrase to recover your ecash balance and wallet address on this device. Separate words by blank spaces.",
|
||||
"recoveryInsertMnemonicDescAddrOnly": "Paste or rewrite 12 word phrase to recover your wallet address on this device. Separate words by blank spaces.",
|
||||
"recoveryInvalidMnemonicError": "Invalid mnemonic phrase. Provide 12 word sequence separated by blank spaces.",
|
||||
"recoveryTool": "Recovery tool",
|
||||
"relayExists": "Relay already exists",
|
||||
"relays": "Relays",
|
||||
"relayurlPasteError": "Copy your relay URL key before pasting",
|
||||
"removeRelay": "Remove relay",
|
||||
"removingMintLostBalanceWarning": "Your wallet has non-zero balance with this mint. If removed, your ecash will be lost!",
|
||||
"resetAborted": "Reset aborted, there are no transactions in local database.",
|
||||
"resetCompletedDetail": "Reset completed. %{transCount} recent transactions were loaded from database.",
|
||||
"resetOwnProfile": "Reset own profile",
|
||||
"resetOwnProfileDesc": "Stop using your own NOSTR address and re-create Minibits wallet profile.",
|
||||
"scanReceiveExtractFail": "Could not extract ecash token nor LNURL withdraw link to receive.",
|
||||
"scanScreen": {
|
||||
"onPasteEmptyClipboard": "First copy ecash token, invoice, LNURL link or lightning address. Then paste."
|
||||
},
|
||||
"securityScreen": {
|
||||
"biometry": "Biometric authentication",
|
||||
"biometryAvailable": "Biometric authentication is now required for Minibits to start and unlock the encryption key.",
|
||||
"biometryNone": "You have not setup biometric authentication or your device does not support it.",
|
||||
"encryptStorage": "Encrypt storage",
|
||||
"encryptStorageDescription": "Encrypt the storage that stores your ecash with the secret key generated on your device. Experimental, not recommended for every day use."
|
||||
},
|
||||
"sendScreen": {
|
||||
"pasteToSend": "Paste",
|
||||
"pasteToSendDescription": "Paste lightning invoice from clipboard to pay it from your wallet.",
|
||||
"scanToPay": "Scan to pay",
|
||||
"scanToPayDescription": "Scan Lightning invoice, LNURL pay code or Lightning address to pay.",
|
||||
"sendToContact": "Send to contact",
|
||||
"sendToContactDescription": "Send your ecash to one of your contacts stored in your contact list.",
|
||||
"showOrShareToken": "Share ecash",
|
||||
"showOrShareTokenDescription": "Present or share ecash token for an amount you want to send.",
|
||||
"title": "Send"
|
||||
},
|
||||
"saveContactPrivate": "Save as private contact",
|
||||
"saveContactPrivateDesc": "Save %{nip05} to your Private contacts so you can pay faster",
|
||||
"setStartIndex": "Set start index",
|
||||
"settingsScreen": {
|
||||
"backupRecovery": "Backup and recovery",
|
||||
"devOptions": "Developer options",
|
||||
"go": "Let's go!",
|
||||
"mintsCount": {
|
||||
"one": "{{count}} mint",
|
||||
"other": "{{count}} mints"
|
||||
},
|
||||
"preferredUnit": "Preferred unit",
|
||||
"privacy": "Privacy",
|
||||
"security": "Security",
|
||||
"title": "Settings",
|
||||
"update": "Update manager"
|
||||
},
|
||||
"share": {
|
||||
"cancelled": "Sharing cancelled",
|
||||
"contactAddress": "Share contact address"
|
||||
},
|
||||
"shareWalletAddress": "Share wallet address",
|
||||
"showErrors": "Show errors",
|
||||
"showOnboarding": "Show onboarding again",
|
||||
"showOnboardingDesc": "Go through onboarding screens again next time you restart Minibits.",
|
||||
"startRecovery": "Start recovery",
|
||||
"syncCompleted": "Sync completed.",
|
||||
"syncOwnProfile": "Sync own profile",
|
||||
"syncOwnProfileDesc": "Synchronize your profile name and picture with up to date information from Nostr relays.",
|
||||
"tabNavigator": {
|
||||
"contactsLabel": "Contacts",
|
||||
"settingsLabel": "Settings",
|
||||
"walletLabel": "Wallet"
|
||||
},
|
||||
"tokenReceiveScreen": {
|
||||
"onPasteEmptyClipboard": "First copy ecash token, then paste.",
|
||||
"topupWithLightning": "Topup with Lightning"
|
||||
},
|
||||
"topup": {
|
||||
"failed": "Topup failed",
|
||||
"mint": "Topup mint",
|
||||
"missingMintAddFirst": "Please add the mint first",
|
||||
"missingMintAddFirstDesc": "There is no mint connected to your wallet eligible for receiving ecash.",
|
||||
"nostrDMreceived": "nostr:%{npub} sent you Lightning invoice for %{amount} %{currency} from Minibits wallet!",
|
||||
"relayMissingSentEvent": "Relay could not confirm that the message has been published.",
|
||||
"sendToNip": "Send to %{sendTonip05}",
|
||||
"to": "Topup to",
|
||||
"withLightning": "Topup with Lightning",
|
||||
"sendToContact": "Send to contact",
|
||||
"withdrawalAvailable": "Withdrawal is available",
|
||||
"withdrawAvailableDesc": "Up to %{amount} %{code} are available to withdraw",
|
||||
"invoiceCreatedParam": "Invoice for %{amount} %{code} created",
|
||||
"withdraw": "Withdraw",
|
||||
"sendRequest": "Send request",
|
||||
"invoiceCreatedDescParam": "Your selected mint balance to top up is %{mintUrl}"
|
||||
},
|
||||
"tranDetailScreen": {
|
||||
"amount": "Amount",
|
||||
"balanceAfter": "Balance after this transaction",
|
||||
"claim": "Claim",
|
||||
"createdAt": "Created at",
|
||||
"id": "ID",
|
||||
"invoice": "Lightning invoice to pay",
|
||||
"isOffline": "Redeem online",
|
||||
"lightningFee": "Lightning network fee",
|
||||
"memoFromInvoice": "Memo from invoice",
|
||||
"memoFromSender": "Memo from sender",
|
||||
"receiveOfflineComplete": "Redeem to wallet",
|
||||
"revert": "Revert",
|
||||
"sentFrom": "Sent from",
|
||||
"sentTo": "Sent to",
|
||||
"status": "Status",
|
||||
"topupTo": "Topup to",
|
||||
"trasferredTo": "Paid to",
|
||||
"type": "Type",
|
||||
"addYourNote": "Add your note",
|
||||
"addTags": "Add tags",
|
||||
"backedUpEcash": "Backed up ecash",
|
||||
"successRetrieveEcashAfterRetry": "Ecash has been successfully received after retry. New transaction has been created.",
|
||||
"retryToReceive": "Retry to receive",
|
||||
"auditTrail": "Audit trail",
|
||||
"modalSuccess": "Success!",
|
||||
"pendingToken": "Pending token",
|
||||
"retryToComplete": "Retry to complete",
|
||||
"invoiceToPay": "Invoice to pay",
|
||||
"invoiceNotPaid": "Invoice not paid",
|
||||
"invoiceExpired": "Invoice expired",
|
||||
"topupError": "Topup error"
|
||||
},
|
||||
"transactionCommon": {
|
||||
"feePaid": "Paid fee",
|
||||
"from": "From %{sender}",
|
||||
"paidFrom": "Paid from",
|
||||
"paidTo": "Paid to %{receiver}",
|
||||
"receivedFrom": "Received from %{sender}",
|
||||
"receivedTo": "Received to",
|
||||
"receiveFailed": "Receive failed",
|
||||
"redeemOnline": "Redeem online",
|
||||
"reverted": "Transaction reverted",
|
||||
"sentTo": "Sent to %{receiver}",
|
||||
"status": {
|
||||
"blocked": "Blocked",
|
||||
"completedFee": "Completed %{fee}",
|
||||
"draft": "Draft",
|
||||
"error": "Error",
|
||||
"expired": "Expired",
|
||||
"pending": "Pending",
|
||||
"prepared": "Prepared",
|
||||
"reverted": "Reverted",
|
||||
"spent": "Spent",
|
||||
"unspent": "Unspent"
|
||||
},
|
||||
"tapToRedeem": "Tap to redeem",
|
||||
"unknown": "Unknown transaction",
|
||||
"youPaid": "You paid",
|
||||
"youReceived": "You received",
|
||||
"youSent": "You sent"
|
||||
},
|
||||
"transferScreen": {
|
||||
"insufficientFunds": "There is not enough balance in %{currency} to pay the invoice amount and expected fees: %{amount} %{currency}",
|
||||
"LUD18unsupported": "Minibits does not yet support entering of payer identity data (LUD18).",
|
||||
"noMintWithBalance": "There is no mint with %{unit} balance from which payment can be made.",
|
||||
"selectMintFrom": "Select mint balance to transfer from"
|
||||
},
|
||||
"updateScreen": {
|
||||
"gotoPlayStore": "Visit Play Store",
|
||||
"nativeUpdateAvailableDesc": "There is a new version of Minibits Wallet in the Google Play Store.",
|
||||
"updateAvailable": "Update is available",
|
||||
"updateAvailableDesc": "There is a new Over-the-Air update of Minibits Wallet ready to download and install.",
|
||||
"updateNew": "What's new?",
|
||||
"updateNotAvailable": "Nothing to update",
|
||||
"updateNotAvailableDesc": "Good, you are running the latest version of the app.",
|
||||
"updateNow": "Update and restart",
|
||||
"updateSize": "Download size",
|
||||
"downloading": "Downloading update...",
|
||||
"installing": "Installing update...",
|
||||
"updateManagerTitle": "Update manager",
|
||||
"defaultUpdateDesc": "UX and performance improvements",
|
||||
"apkOnGithub": "APK on Github"
|
||||
},
|
||||
"userErrorMissingLightningData": "Missing Lightning invoice, paycode or address.",
|
||||
"unspentOnlyRecoverable": "Only unspent proofs are recoverable.",
|
||||
"walletAddressRecovery": "Wallet address recovery",
|
||||
"walletAddressRecoveryDesc": "Use your mnemonic phrase from another device to get your original wallet address on this device. This will not recover balances.",
|
||||
"walletScreen": {
|
||||
"feeBadge": {
|
||||
"final": "+ final fee %{fee} %{code}",
|
||||
"upto": "+ fee up to %{fee} %{code}"
|
||||
},
|
||||
"mintExists": "This mint already exists",
|
||||
"paymentSentSuccess": "Payment request has been successfully sent.",
|
||||
"sendEcash": "Send Ecash",
|
||||
"sendEcashDesc": "Share ecash or send it to one of your contacts",
|
||||
"payWithLightning": "Pay with Lightning",
|
||||
"payWithLightningDesc": "Pay invoice or to a Lightning address",
|
||||
"receiveEcash": "Receive Ecash",
|
||||
"topupWithLightning": "Topup with Lightning",
|
||||
"topupWithLightningDesc": "Create Lightning invoice to topup your balance",
|
||||
"receiveEcashDesc": "Paste or scan ecash token",
|
||||
"addFirstMint": "Add your first mint",
|
||||
"topup": "Topup",
|
||||
"pay": "Pay"
|
||||
},
|
||||
"warning": "Warning",
|
||||
"welcomeScreen": {
|
||||
"page1": {
|
||||
"bullet1": "Minibits follows the Cashu protocol, where mints back ecash with Bitcoin.",
|
||||
"bullet2": "Ecash is issued or exchanged back to Bitcoin instantly through Lightning payments.",
|
||||
"bullet3": "Ecash tokens are stored on-device; mints do not keep ledger or wallet balances.",
|
||||
"final": "No Watson, there is no blockchain.",
|
||||
"heading": "Welcome",
|
||||
"intro": "Minibits is an ecash and Lightning wallet with a focus on performance and usability. Ecash is a bearer token issued by custodians known as mints."
|
||||
},
|
||||
"page2": {
|
||||
"bullet1": "Minibits provides sharable wallet identifiers using NOSTR addresses. Like account numbers, just better.",
|
||||
"bullet2": "Try in-person sends while devices are offline - ecash settles when back online (Proof of concept).",
|
||||
"bullet3": "You can opt-in for storage encryption and biometric authentication.",
|
||||
"final": "Minibits is free and open-source software; find us on Github for the roadmap and contributions.",
|
||||
"heading": "Why Minibits?",
|
||||
"intro": "Minibits' aim is to research how Lightning and ecash can be integrated into a seamless and instant experience that can work at scale and still provide good privacy."
|
||||
},
|
||||
"page3": {
|
||||
"bullet1": "Mints are, by design, custodial services. Run your own or use them only for research and testing purposes.",
|
||||
"bullet2": "Ecash is stored on your device, so the loss of the device means the loss of ecash. Remote backup protocol is still under research.",
|
||||
"bullet3": "Minibits provides its own mint that you can use for testing purposes with small amounts. It is operated on a best-effort basis and without any guarantees.",
|
||||
"final": "Now, let's move some ecash!",
|
||||
"go": "Let's go!",
|
||||
"heading": "Do not forget",
|
||||
"intro": "Both the Cashu protocol and the Minibits wallet are still experimental, and by using them, you accept known and unknown risks."
|
||||
},
|
||||
"lastPageConfirmButton": "Got it, take me to the wallet",
|
||||
"lastPageRecoverLostWalletButton": "Recover lost wallet",
|
||||
"generatingWalletSeedStatusMessage": "Creating wallet seed, this takes a while..."
|
||||
},
|
||||
"contactHasNoLightningAddrUseEcash": "This contact does not have a Lightning address, send ecash instead.",
|
||||
"tranHistory": {
|
||||
"showingPaginationPending": "Showing %{amount} of %{total} pending",
|
||||
"showingPaginationTotal": "Showing %{amount} of %{total} total",
|
||||
"pendingParam": "%{param} Pending transactions",
|
||||
"listIsComplete": "List is complete",
|
||||
"viewMore": "View more",
|
||||
"noTransToShow": "No transactions to show.",
|
||||
"deleteExpired": "Delete expired",
|
||||
"deleteExpiredDesc": "This will delete %{count} expired transactions",
|
||||
"deleteErrored": "Delete transactions with errors",
|
||||
"deleteErroredDesc": "This will delete %{count} transactions with errors"
|
||||
},
|
||||
"updateModal": {
|
||||
"title": "New Minibits version is available",
|
||||
"desc": "Updates provide new functionalities and important bug fixes. View details in the Update manager."
|
||||
},
|
||||
"wallerScreen": {
|
||||
"mintButton": "Mint"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"common": {
|
||||
"close": "Zavrieť",
|
||||
"copyFailParam": "Nepodarilo sa skopírovať %{param}",
|
||||
"notCreated": "Ešte nevytvorené",
|
||||
"offlinePretty": "Vaše zariadenie je momentálne offline.",
|
||||
"qr": "QR kód",
|
||||
"resetDefault": "Obnoviť pôvodné nastavenia",
|
||||
"sats": "SATS",
|
||||
"share": "Zdieľať"
|
||||
},
|
||||
"transferScreen": {
|
||||
"noMintWithBalance": "Neexistuje žiaden mint so zostatkom %{unit} z ktorého je možné uskutočniť platbu."
|
||||
},
|
||||
"contactHasNoLightningAddrUseEcash": "Tento kontakt nemá Lightning adresu, namiesto toho pošlite ecash."
|
||||
}
|
||||
@@ -22,6 +22,7 @@ import EventEmitter from '../utils/eventEmitter'
|
||||
import {ResultModalInfo} from './Wallet/ResultModalInfo'
|
||||
import {Database, log, WalletTask, WalletTaskResult} from '../services'
|
||||
import { getSnapshot } from 'mobx-state-tree'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(function BackupScreen(_props) {
|
||||
const {navigation} = _props
|
||||
@@ -50,9 +51,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
const handleSpentByMintTaskResult = async (result: WalletTaskResult) => {
|
||||
log.warn('handleSpentByMintTaskResult event handler triggered')
|
||||
|
||||
if(!isHandleSpentFromSpendableSentToQueue) {
|
||||
return false
|
||||
}
|
||||
if (!isHandleSpentFromSpendableSentToQueue) { return false }
|
||||
|
||||
setIsLoading(false)
|
||||
// runs per each mint
|
||||
@@ -60,12 +59,11 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
setTotalSpentAmount(prev => prev + result.spentAmount)
|
||||
setTotalSpentCount(prev => prev + result.spentCount)
|
||||
setInfo(
|
||||
`${totalSpentCount} ecash proofs, ${totalSpentAmount} in total were removed from the wallet.`,
|
||||
translate("backupScreen.result", { proofCount: totalSpentCount, amount: totalSpentAmount })
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
setInfo('No spent ecash found in your wallet')
|
||||
setInfo(translate("noSpentEcashFound"))
|
||||
}
|
||||
|
||||
EventEmitter.on('ev__handleSpentByMintTask_result', handleSpentByMintTaskResult)
|
||||
@@ -93,9 +91,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
Database.addOrUpdateProofs(proofsStore.allPendingProofs, true)
|
||||
}
|
||||
|
||||
setBackupResultMessage(
|
||||
'Your minibits tokens were backed up to local database. New tokens will be backed up automatically.',
|
||||
)
|
||||
setBackupResultMessage(translate("backupScreen.success"))
|
||||
toggleBackupModal()
|
||||
setIsLoading(false)
|
||||
return
|
||||
@@ -103,9 +99,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
|
||||
Database.removeAllProofs()
|
||||
setIsLoading(false)
|
||||
setBackupResultMessage(
|
||||
'Your backup of minibits tokens has been deleted. New tokens will NOT be backed up.',
|
||||
)
|
||||
setBackupResultMessage(translate("backupScreen.deletedSuccess"))
|
||||
toggleBackupModal()
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
@@ -135,12 +129,13 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
|
||||
|
||||
const increaseCounters = async function () {
|
||||
for (const mint of mintsStore.allMints) {
|
||||
for(const counter of mint.proofsCounters) {
|
||||
mint.increaseProofsCounter(counter.keyset, 50)
|
||||
}
|
||||
}
|
||||
setInfo('Recovery indexes increased by 50')
|
||||
const increaseAmount = 50
|
||||
for (const mint of mintsStore.allMints) {
|
||||
for(const counter of mint.proofsCounters) {
|
||||
mint.increaseProofsCounter(counter.keyset, increaseAmount)
|
||||
}
|
||||
}
|
||||
setInfo(translate("recoveryIndexesIncSuccess", { indCount: increaseAmount }))
|
||||
}
|
||||
|
||||
|
||||
@@ -172,8 +167,8 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
text="Wallet address recovery"
|
||||
subText="Use your mnemonic phrase from another device to get your original wallet address on this device. This will not recover balances."
|
||||
tx="walletAddressRecovery"
|
||||
subTx="walletAddressRecoveryDesc"
|
||||
leftIcon='faCircleUser'
|
||||
leftIconColor={colors.palette.iconGreyBlue400}
|
||||
leftIconInverse={true}
|
||||
@@ -251,8 +246,8 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
HeadingComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text="Increase recovery indexes"
|
||||
subText={`After incomplete recovery from seed or other rare issue, you may encounter 'duplicate outputs' error when trying to send. This resets your recovery indexes to higher value in order to resolve the issue.`}
|
||||
tx="increaseRecoveryIndexes"
|
||||
subTx="increaseRecoveryIndexesDesc"
|
||||
leftIcon='faArrowUp'
|
||||
leftIconColor={colors.palette.success300}
|
||||
leftIconInverse={true}
|
||||
@@ -283,9 +278,10 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
? colors.palette.success200
|
||||
: colors.palette.neutral400
|
||||
}
|
||||
title={
|
||||
isLocalBackupOn ? 'Local backup is on' : 'Local backup is off'
|
||||
}
|
||||
// title={
|
||||
// isLocalBackupOn ? 'Local backup is on' : 'Local backup is off'
|
||||
// }
|
||||
title={translate(isLocalBackupOn ? 'localBackupEnabled' : 'localBackupDisabled')}
|
||||
message={backupResultMessage as string}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { moderateVerticalScale, verticalScale } from '@gocodingnow/rn-size-matte
|
||||
import { ReceiveOption } from './ReceiveOptionsScreen'
|
||||
import { SendOption } from './SendOptionsScreen'
|
||||
import { IncomingDataType, IncomingParser } from '../services/incomingParser'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
|
||||
interface ContactDetailScreenProps extends ContactsStackScreenProps<'ContactDetail'> {}
|
||||
@@ -94,10 +95,10 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
|
||||
const onNoteSave = function () {
|
||||
try {
|
||||
contactsStore.saveNote(contact.pubkey, note || '')
|
||||
setIsNoteEditing(false)
|
||||
contactsStore.saveNote(contact.pubkey, note || '')
|
||||
setIsNoteEditing(false)
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not save note: ${e.message}`)
|
||||
setInfo(translate('common.saveFailParam', { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,9 +116,9 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
|
||||
const onCopyNpub = function () {
|
||||
try {
|
||||
Clipboard.setString(contact.npub)
|
||||
Clipboard.setString(contact.npub)
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not copy: ${e.message}`)
|
||||
setInfo(translate('common.copyFailParam', { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +144,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
contactsStore.refreshPicture(contact.pubkey)
|
||||
|
||||
toggleContactModal()
|
||||
setInfo('Sync completed.')
|
||||
setInfo(translate("syncCompleted"))
|
||||
return
|
||||
} catch (e: any) {
|
||||
toggleContactModal()
|
||||
@@ -222,7 +223,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
maxLength={200}
|
||||
keyboardType="default"
|
||||
selectTextOnFocus={true}
|
||||
placeholder="Your private note"
|
||||
placeholder={translate("privateNotePlaceholder")}
|
||||
editable={
|
||||
isNoteEditing
|
||||
? true
|
||||
@@ -258,7 +259,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
{lud16 && (
|
||||
<ListItem
|
||||
text={lud16}
|
||||
subText={'Lightning address'}
|
||||
subTx='lightningAddress'
|
||||
leftIcon='faBolt'
|
||||
leftIconColor={colors.palette.orange200}
|
||||
/>
|
||||
@@ -266,7 +267,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
<ListItem
|
||||
LeftComponent={
|
||||
<Button
|
||||
text={`Request payment`}
|
||||
tx="payCommon.requestPayment"
|
||||
style={{marginLeft: spacing.small, alignSelf: 'center', minHeight: verticalScale(20)}}
|
||||
textStyle={{fontSize: moderateVerticalScale(14), lineHeight: moderateVerticalScale(16)}}
|
||||
onPress={gotoTopup}
|
||||
@@ -275,7 +276,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
}
|
||||
RightComponent={lud16 ? (
|
||||
<Button
|
||||
text={`Pay to this address`}
|
||||
tx="payCommon.payToAddress"
|
||||
style={{marginLeft: spacing.small, alignSelf: 'center', minHeight: verticalScale(20)}}
|
||||
textStyle={{fontSize: moderateVerticalScale(14), lineHeight: moderateVerticalScale(16)}}
|
||||
onPress={gotoTransfer}
|
||||
@@ -307,7 +308,7 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
<Text
|
||||
size='xs'
|
||||
style={{color: addressColor, marginHorizontal: spacing.large, textAlign: 'center'}}
|
||||
text='This profile does not have nostr address to send ecash to.'
|
||||
tx="profileMissingNostrAddress"
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
@@ -318,14 +319,14 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text='Share contact address'
|
||||
tx="share.contactAddress"
|
||||
subText={nip05}
|
||||
leftIcon='faShareNodes'
|
||||
onPress={onShareContact}
|
||||
bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
text="Copy contact's public key"
|
||||
tx="copyContactPublicKey"
|
||||
subText={npub}
|
||||
leftIcon='faCopy'
|
||||
onPress={onCopyNpub}
|
||||
@@ -333,16 +334,16 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
{type === ContactType.PRIVATE && (
|
||||
<>
|
||||
<ListItem
|
||||
text='Check and sync'
|
||||
subText='Checks that contact name is still linked to the same pubkey and updates picture if it was changed.'
|
||||
tx="checkAndSync"
|
||||
subTx="checkAndSyncDesc"
|
||||
leftIcon='faRotate'
|
||||
onPress={onSyncPrivateContact}
|
||||
topSeparator={true}
|
||||
bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
text='Delete contact'
|
||||
subText='Remove this contact from your wallet.'
|
||||
tx="deleteContact"
|
||||
subTx="deleteContactDesc"
|
||||
leftIcon='faXmark'
|
||||
onPress={onDeleteContact}
|
||||
/>
|
||||
@@ -350,8 +351,8 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
)}
|
||||
{type === ContactType.PUBLIC && (
|
||||
<ListItem
|
||||
text='Save as private contact'
|
||||
subText={`Save ${contact.nip05} to your Private contacts so you can pay faster`}
|
||||
tx="saveContactPrivate"
|
||||
subText={translate('saveContactPrivateDesc', { nip05: contact.nip05 })}
|
||||
leftIcon='faClipboard'
|
||||
onPress={saveToPrivateContacts}
|
||||
topSeparator={true}
|
||||
|
||||
@@ -98,23 +98,23 @@ export const OwnName = observer(function (props: {navigation: any, pubkey: strin
|
||||
}, [donationInvoice])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const handleIsInvoicePaid = async () => {
|
||||
if(!isInvoicePaid || !donationInvoice) {
|
||||
return
|
||||
}
|
||||
|
||||
stopPolling(`checkDonationPaidPoller-${donationInvoice.payment_hash}`)
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.COMPLETED,
|
||||
message: translate("contactsScreen.ownName.donationSuccess", { receiver: ownName+MINIBITS_NIP05_DOMAIN })
|
||||
})
|
||||
toggleResultModal()
|
||||
//resetState()
|
||||
useEffect(() => {
|
||||
const handleIsInvoicePaid = async () => {
|
||||
if (!isInvoicePaid || !donationInvoice) {
|
||||
return
|
||||
}
|
||||
handleIsInvoicePaid()
|
||||
return () => {
|
||||
}
|
||||
|
||||
stopPolling(`checkDonationPaidPoller-${donationInvoice.payment_hash}`)
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.COMPLETED,
|
||||
message: translate("contactsScreen.ownName.donationSuccess", { receiver: ownName + MINIBITS_NIP05_DOMAIN })
|
||||
})
|
||||
toggleResultModal()
|
||||
//resetState()
|
||||
}
|
||||
handleIsInvoicePaid()
|
||||
return () => {
|
||||
}
|
||||
}, [isInvoicePaid])
|
||||
|
||||
const toggleResultModal = () =>
|
||||
|
||||
@@ -118,7 +118,9 @@ export const PrivateContacts = observer(function (props: {
|
||||
const profileRecord = await MinibitsClient.getWalletProfileByNip05(newContactName + MINIBITS_NIP05_DOMAIN)
|
||||
|
||||
if(!profileRecord) {
|
||||
warningMessage(translate("contactsScreen.privateContacts.profileNotFound", { name: newContactName + MINIBITS_NIP05_DOMAIN }))
|
||||
warningMessage(translate("contactsScreen.privateContacts.profileNotFound", {
|
||||
name: newContactName + MINIBITS_NIP05_DOMAIN
|
||||
}))
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
@@ -204,7 +206,7 @@ export const PrivateContacts = observer(function (props: {
|
||||
|
||||
if(paymentOption && paymentOption === SendOption.LNURL_ADDRESS) {
|
||||
if(!contact.lud16) {
|
||||
setInfo(translate("contactsScreen.privateContacts.noLightningAddress"))
|
||||
setInfo(translate("contactHasNoLightningAddrUseEcash"))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -265,7 +265,7 @@ export const PublicContacts = observer(function (props: {
|
||||
const onPastePublicRelay = async function () {
|
||||
const url = await Clipboard.getString()
|
||||
if (!url) {
|
||||
setInfo(translate("contactsScreen.publicContacts.relayurlPasteError"))
|
||||
setInfo(translate("relayurlPasteError"))
|
||||
return
|
||||
}
|
||||
setNewPublicRelay(url)
|
||||
@@ -276,7 +276,7 @@ export const PublicContacts = observer(function (props: {
|
||||
try {
|
||||
if(newPublicRelay) {
|
||||
if(relaysStore.alreadyExists(newPublicRelay)) {
|
||||
setInfo(translate("contactsScreen.publicContacts.relayExists"))
|
||||
setInfo(translate("relayExists"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -381,7 +381,7 @@ export const PublicContacts = observer(function (props: {
|
||||
|
||||
if(paymentOption && paymentOption === SendOption.LNURL_ADDRESS) {
|
||||
if(!contact.lud16) {
|
||||
setInfo(translate('contactsScreen.publicContacts.missingLightningAddress'))
|
||||
setInfo(translate('contactHasNoLightningAddrUseEcash'))
|
||||
//reset
|
||||
navigation.setParams({
|
||||
paymentOption: undefined,
|
||||
@@ -440,8 +440,8 @@ export const PublicContacts = observer(function (props: {
|
||||
leftIcon='faComment'
|
||||
leftIconInverse={true}
|
||||
leftIconColor={colors.palette.iconViolet200}
|
||||
tx="contactsScreen.publicContacts.nostrTip"
|
||||
subTx='contactsScreen.publicContacts.nostrTipSubText'
|
||||
tx="nostr.tip"
|
||||
subTx='nostr.tipSubText'
|
||||
onPress={toggleNpubModal}
|
||||
/>
|
||||
}
|
||||
@@ -523,22 +523,22 @@ export const PublicContacts = observer(function (props: {
|
||||
<>
|
||||
<ListItem
|
||||
leftIcon='faKey'
|
||||
tx="contactsScreen.publicContacts.nostrSetPublicKey"
|
||||
subTx='contactsScreen.publicContacts.nostrSetPublicKeySubText'
|
||||
tx="nostr.setPublicKey"
|
||||
subTx='nostr.setPublicKeySubText'
|
||||
onPress={toggleNpubModal}
|
||||
bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
leftIcon='faCircleNodes'
|
||||
tx='contactsScreen.publicContacts.nostrSetRelay'
|
||||
subTx='contactsScreen.publicContacts.nostrSetRelaySubText'
|
||||
tx='nostr.setRelay'
|
||||
subTx='nostr.setRelaySubText'
|
||||
onPress={toggleRelayModal}
|
||||
bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
leftIcon='faBan'
|
||||
tx="contactsScreen.publicContacts.nostrRemovePub"
|
||||
subTx="contactsScreen.publicContacts.nostrRemovePubSubText"
|
||||
tx="nostr.removePub"
|
||||
subTx="nostr.removePubSubText"
|
||||
onPress={onRemovePublicPubKey}
|
||||
/>
|
||||
</>
|
||||
|
||||
@@ -78,13 +78,11 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
|
||||
transactionsStore.addTransactionsToModel(result._array)
|
||||
|
||||
setIsLoading(false)
|
||||
setInfo(
|
||||
`Reset completed. ${result.length} recent transactions were loaded from database.`,
|
||||
)
|
||||
setInfo(translate('resetCompletedDetail', { transCount: result.length }))
|
||||
return true
|
||||
}
|
||||
|
||||
setInfo('Reset aborted, there are no transactions in local database.')
|
||||
setInfo(translate("resetAborted"))
|
||||
setIsLoading(false)
|
||||
return false
|
||||
} catch (e: any) {
|
||||
@@ -104,45 +102,42 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
const factoryReset = async function () {
|
||||
Alert.alert(
|
||||
'Confirmation',
|
||||
'All application data will be deleted. Are you sure you want to perform a factory reset?',
|
||||
translate("common.confirmAlertTitle"),
|
||||
translate("factoryResetUserConfirmDesc"),
|
||||
[
|
||||
{
|
||||
text: 'Cancel',
|
||||
text: translate('common.cancel'),
|
||||
style: 'cancel',
|
||||
onPress: () => {
|
||||
// Action canceled
|
||||
},
|
||||
onPress: () => { /* Action canceled */ },
|
||||
},
|
||||
{
|
||||
text: 'Confirm',
|
||||
text: translate('common.confirm'),
|
||||
onPress: async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
// Delete database
|
||||
Database.cleanAll()
|
||||
// Delete Nostr keys
|
||||
await KeyChain.removeNostrKeypair()
|
||||
// Delete Encryption key
|
||||
await KeyChain.removeMmkvEncryptionKey()
|
||||
// Delete mnemonic
|
||||
await KeyChain.removeMnemonic()
|
||||
// Delete seed
|
||||
await KeyChain.removeSeed()
|
||||
// Clean mobx storage
|
||||
MMKVStorage.clearAll()
|
||||
// recreate db schema
|
||||
Database.getInstance()
|
||||
setIsLoading(false)
|
||||
setInfo('Factory reset completed, please restart immediately.')
|
||||
// Delete database
|
||||
Database.cleanAll()
|
||||
// Delete Nostr keys
|
||||
await KeyChain.removeNostrKeypair()
|
||||
// Delete Encryption key
|
||||
await KeyChain.removeMmkvEncryptionKey()
|
||||
// Delete mnemonic
|
||||
await KeyChain.removeMnemonic()
|
||||
// Delete seed
|
||||
await KeyChain.removeSeed()
|
||||
// Clean mobx storage
|
||||
MMKVStorage.clearAll()
|
||||
// recreate db schema
|
||||
Database.getInstance()
|
||||
setIsLoading(false)
|
||||
setInfo(translate("factoryResetSuccess"))
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
handleError(e)
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -199,8 +194,8 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
|
||||
onPress={syncTransactionsFromDb}
|
||||
/>
|
||||
<ListItem
|
||||
text="Show onboarding again"
|
||||
subText="Go through onboarding screens again next time you restart Minibits."
|
||||
tx="showOnboarding"
|
||||
subTx="showOnboardingDesc"
|
||||
leftIcon='faRotate'
|
||||
leftIconColor={colors.light.tint}
|
||||
leftIconInverse={true}
|
||||
@@ -255,7 +250,7 @@ Sentry id: ${userSettingsStore.userSettings.walletId}
|
||||
<>
|
||||
<ListItem
|
||||
text={LogLevel.ERROR.toUpperCase()}
|
||||
subText={'Log errors and crashes only, without private data.'}
|
||||
subTx="loglevelErrorDesc"
|
||||
leftIcon={selectedLogLevel === LogLevel.ERROR ? 'faCheckCircle' : 'faCircle'}
|
||||
leftIconColor={selectedLogLevel === LogLevel.ERROR ? iconSelectedColor as string : iconColor as string}
|
||||
onPress={() => onLogLevelSelect(LogLevel.ERROR)}
|
||||
@@ -264,7 +259,7 @@ Sentry id: ${userSettingsStore.userSettings.walletId}
|
||||
/>
|
||||
<ListItem
|
||||
text={LogLevel.INFO.toUpperCase()}
|
||||
subText={'Log anonymous usage, without private data.'}
|
||||
subTx="loglevelInfoDesc"
|
||||
leftIcon={selectedLogLevel === LogLevel.INFO ? 'faCheckCircle' : 'faCircle'}
|
||||
leftIconColor={selectedLogLevel === LogLevel.INFO ? iconSelectedColor as string : iconColor as string}
|
||||
onPress={() => onLogLevelSelect(LogLevel.INFO)}
|
||||
@@ -273,7 +268,7 @@ Sentry id: ${userSettingsStore.userSettings.walletId}
|
||||
/>
|
||||
<ListItem
|
||||
text={LogLevel.DEBUG.toUpperCase()}
|
||||
subText={'Log details, set only if testing or on request by support.'}
|
||||
subTx="loglevelDebugDesc"
|
||||
leftIcon={selectedLogLevel === LogLevel.DEBUG ? 'faCheckCircle' : 'faCircle'}
|
||||
leftIconColor={selectedLogLevel === LogLevel.DEBUG ? iconSelectedColor as string : iconColor as string}
|
||||
onPress={() => onLogLevelSelect(LogLevel.DEBUG)}
|
||||
@@ -283,7 +278,7 @@ Sentry id: ${userSettingsStore.userSettings.walletId}
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
text={'Close'}
|
||||
tx='common.close'
|
||||
onPress={toggleLogLevelSelector}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Mint } from '../models/Mint'
|
||||
import { MintHeader } from './Mints/MintHeader'
|
||||
import { moderateVerticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
|
||||
export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = function LightningPayScreen(_props) {
|
||||
@@ -46,20 +47,16 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
const setUnitAndMint = () => {
|
||||
try {
|
||||
const {unit, mintUrl} = route.params
|
||||
if(!unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint unit in route params')
|
||||
}
|
||||
if (!unit) throw new AppError(Err.VALIDATION_ERROR, 'Missing mint unit in route params');
|
||||
|
||||
setUnit(unit)
|
||||
|
||||
if(mintUrl) {
|
||||
const mint = mintsStore.findByUrl(mintUrl)
|
||||
setMint(mint)
|
||||
if (mintUrl) {
|
||||
const mint = mintsStore.findByUrl(mintUrl)
|
||||
setMint(mint)
|
||||
}
|
||||
|
||||
if(!isInternetReachable) {
|
||||
setInfo('Your device is currently offline.')
|
||||
}
|
||||
if (!isInternetReachable) setInfo(translate('common.offlinePretty'))
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
@@ -82,7 +79,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
const onPaste = async function() {
|
||||
const clipboard = await Clipboard.getString()
|
||||
if (clipboard.length === 0) {
|
||||
infoMessage('First copy Lightning invoice, address or pay code to pay to. Then paste.')
|
||||
infoMessage(translate('lightningPayScreen.onPasteEmptyClipboard'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -105,15 +102,14 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
|
||||
|
||||
const onConfirm = async function() {
|
||||
if(!lightningData) {
|
||||
setError({name: Err.VALIDATION_ERROR, message: 'Missing Lightning invoice, paycode or address.'})
|
||||
return
|
||||
if (!lightningData) {
|
||||
setError({ name: Err.VALIDATION_ERROR, message: translate("userErrorMissingLightningData")})
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const invoiceResult = IncomingParser.findAndExtract(lightningData as string, IncomingDataType.INVOICE)
|
||||
return IncomingParser.navigateWithIncomingData(invoiceResult, navigation, unit, mint && mint.mintUrl)
|
||||
|
||||
const invoiceResult = IncomingParser.findAndExtract(lightningData as string, IncomingDataType.INVOICE)
|
||||
return IncomingParser.navigateWithIncomingData(invoiceResult, navigation, unit, mint && mint.mintUrl)
|
||||
} catch (e: any) {
|
||||
const maybeLnurlAddress = LnurlUtils.findEncodedLnurlAddress(lightningData as string)
|
||||
|
||||
@@ -204,14 +200,14 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
<ListItem
|
||||
leftIcon='faBolt'
|
||||
leftIconColor={colors.palette.orange400}
|
||||
text='Pay with Lightning'
|
||||
tx="payCommon.payWithLightning"
|
||||
bottomSeparator={true}
|
||||
RightComponent={
|
||||
<Button
|
||||
preset='tertiary'
|
||||
LeftAccessory={() => <Icon color={contactIcon} containerStyle={{paddingVertical: 0}} icon='faAddressBook' />}
|
||||
onPress={gotoContacts}
|
||||
text='Contacts'
|
||||
tx="contacts"
|
||||
textStyle={{fontSize: 12, color: contactIcon}}
|
||||
/>
|
||||
}
|
||||
@@ -222,7 +218,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
<Text
|
||||
size='xs'
|
||||
style={{color: hintText, padding: spacing.extraSmall}}
|
||||
text='Enter or paste Lightning address, invoice or LNURL pay code you want to pay to.'
|
||||
tx="payWithLightningDesc"
|
||||
/>
|
||||
<View style={{alignItems: 'center', marginTop: spacing.small}}>
|
||||
<TextInput
|
||||
@@ -242,7 +238,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset='default'
|
||||
tx={'common.confirm'}
|
||||
tx='common.confirm'
|
||||
onPress={onConfirm}
|
||||
style={{marginLeft: spacing.small}}
|
||||
LeftAccessory={() => <Icon icon='faCheckCircle' color='white'/>}
|
||||
@@ -252,11 +248,9 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset='secondary'
|
||||
tx={'common.paste'}
|
||||
tx='common.paste'
|
||||
onPress={onPaste}
|
||||
LeftAccessory={() => (
|
||||
<Icon icon='faPaste'/>
|
||||
)}
|
||||
LeftAccessory={() => <Icon icon='faPaste'/> }
|
||||
/>
|
||||
<Button
|
||||
preset='secondary'
|
||||
@@ -281,7 +275,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
text={'Send as ecash'}
|
||||
tx="common.sendAsEcash"
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faMoneyBill1'
|
||||
|
||||
@@ -38,6 +38,7 @@ import { WalletUtils } from '../services/wallet/utils'
|
||||
import { MintUnit, MintUnits, getCurrency } from '../services/wallet/currency'
|
||||
import { ScrollView } from 'react-native-gesture-handler'
|
||||
import { CurrencyAmount } from './Wallet/CurrencyAmount'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
interface LocalRecoveryScreenProps
|
||||
extends SettingsStackScreenProps<'LocalRecovery'> {}
|
||||
@@ -138,8 +139,8 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
setIsLoading(true)
|
||||
const encodedTokens: string[] = []
|
||||
|
||||
if(mintsStore.allMints.length === 0) {
|
||||
setInfo('Please add all mints your proofs belong to the wallet. Then try again.')
|
||||
if (mintsStore.allMints.length === 0) {
|
||||
setInfo(translate("missingMintsForProofsUserMessage"))
|
||||
}
|
||||
|
||||
for (const mint of mintsStore.allMints) {
|
||||
@@ -180,14 +181,14 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
setIsLoading(false)
|
||||
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not copy: ${e.message}`)
|
||||
setInfo(translate('common.copyFailParam', { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const onRecovery = async function () {
|
||||
if(!showUnspentOnly) {
|
||||
setInfo('Can not recover other then unspent proofs.')
|
||||
if (!showUnspentOnly) {
|
||||
setInfo(translate("unspentOnlyRecoverable"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -199,33 +200,34 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
log.trace('[onRecovery]', nonZeroBalances)
|
||||
|
||||
if (nonZeroBalances && nonZeroBalances.length > 0) {
|
||||
message = `Your wallet has non zero balance. If you continue, existing ecash will be deleted and replaced by backup.\n\n`
|
||||
message = translate("backupWillOverwriteBalanceWarning")
|
||||
message += "\n\n"
|
||||
}
|
||||
|
||||
message += `Do you really want to recover ecash from the backup?`
|
||||
message += translate("confirmBackupRecovery")
|
||||
|
||||
Alert.alert(
|
||||
'Attention!',
|
||||
message,
|
||||
[
|
||||
translate("attention"),
|
||||
message,
|
||||
[
|
||||
{
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
onPress: () => {
|
||||
text: translate('common.cancel'),
|
||||
style: 'cancel',
|
||||
onPress: () => {
|
||||
// Action canceled
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Start recovery',
|
||||
onPress: () => {
|
||||
try {
|
||||
doLocalRecovery()
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
},
|
||||
text: translate("startRecovery"),
|
||||
onPress: () => {
|
||||
try {
|
||||
doLocalRecovery()
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
},
|
||||
},
|
||||
],
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
@@ -233,12 +235,12 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
const doLocalRecovery = async function () {
|
||||
try {
|
||||
if(!showUnspentOnly) {
|
||||
setInfo('Can not recover other then unspent proofs.')
|
||||
setInfo(translate('unspentOnlyRecoverable'))
|
||||
return
|
||||
}
|
||||
|
||||
if(mintsStore.allMints.length === 0) {
|
||||
setInfo('Please add all your mints to the wallet before recovery. Then try again.')
|
||||
setInfo(translate('missingMintsForProofsUserMessage'))
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
@@ -249,16 +251,14 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
let transactionData: TransactionData[] = []
|
||||
|
||||
for (const proof of proofs) {
|
||||
const proofMint = CashuUtils.getMintFromProof(proof, mintsStore.allMints)
|
||||
const { isPending, isSpent, updatedAt, ...cleanedProof } = proof
|
||||
|
||||
const proofMint = CashuUtils.getMintFromProof(proof, mintsStore.allMints)
|
||||
|
||||
const { isPending, isSpent, updatedAt, ...cleanedProof } = proof
|
||||
if (!proofMint) { continue }
|
||||
|
||||
if (!proofMint) { continue }
|
||||
|
||||
if(mint.mintUrl === proofMint.mintUrl) {
|
||||
proofsByMint.push(cleanedProof)
|
||||
}
|
||||
if (mint.mintUrl === proofMint.mintUrl) {
|
||||
proofsByMint.push(cleanedProof)
|
||||
}
|
||||
}
|
||||
|
||||
if (proofsByMint.length > 0) {
|
||||
@@ -371,7 +371,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text
|
||||
preset="heading"
|
||||
text="Recovery tool"
|
||||
tx="recoveryTool"
|
||||
style={{color: 'white'}}
|
||||
/>
|
||||
</View>
|
||||
@@ -381,7 +381,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text={'Unspent'}
|
||||
tx="transactionCommon.status.unspent"
|
||||
LeftComponent={
|
||||
<Icon
|
||||
containerStyle={$iconContainer}
|
||||
@@ -395,7 +395,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
// bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
text={'Pending'}
|
||||
tx="transactionCommon.status.pending"
|
||||
LeftComponent={
|
||||
<Icon
|
||||
containerStyle={$iconContainer}
|
||||
@@ -409,7 +409,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
// bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
text={'Spent'}
|
||||
tx="transactionCommon.status.spent"
|
||||
LeftComponent={
|
||||
<Icon
|
||||
containerStyle={$iconContainer}
|
||||
@@ -429,7 +429,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text={'Number of backed up proofs'}
|
||||
tx="numberOfBackedupProofs"
|
||||
RightComponent={
|
||||
<Text text={`${proofs.length}`} style={{marginRight: spacing.small}} />
|
||||
}
|
||||
@@ -458,7 +458,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={copyBackupProofs}
|
||||
text="Copy proofs"
|
||||
tx="copyProofs"
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
@@ -470,7 +470,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={copyEncodedTokens}
|
||||
text="Copy as encoded tokens"
|
||||
tx="copyAsEncodedTokens"
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
@@ -487,7 +487,7 @@ export const LocalRecoveryScreen: FC<LocalRecoveryScreenProps> =
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
onPress={onRecovery}
|
||||
text={`Recover to wallet`}
|
||||
tx="recoverToWallet"
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -30,9 +30,8 @@ import { CommonActions } from '@react-navigation/native'
|
||||
import { StackActions } from '@react-navigation/native';
|
||||
import { isObj } from '@cashu/cashu-ts/src/utils'
|
||||
|
||||
if (Platform.OS === 'android' &&
|
||||
UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true)
|
||||
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true)
|
||||
}
|
||||
|
||||
export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer(function MintInfoScreen(_props) {
|
||||
@@ -122,7 +121,7 @@ export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text
|
||||
preset="heading"
|
||||
text="Mint info"
|
||||
tx="mintInfoHeading"
|
||||
style={{color: 'white'}}
|
||||
/>
|
||||
</View>
|
||||
@@ -147,7 +146,7 @@ export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer
|
||||
))
|
||||
)}
|
||||
|
||||
{isLoading && <Loading style={{backgroundColor: 'transparent'}} statusMessage='Loading public info' />}
|
||||
{isLoading && <Loading style={{backgroundColor: 'transparent'}} statusMessage={translate("loadingPublicInfo")} />}
|
||||
</>
|
||||
}
|
||||
style={$card}
|
||||
@@ -157,15 +156,15 @@ export const MintInfoScreen: FC<SettingsStackScreenProps<'MintInfo'>> = observer
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text={'On device information'}
|
||||
RightComponent={<View style={$rightContainer}>
|
||||
<Button
|
||||
onPress={toggleLocalInfo}
|
||||
text={isLocalInfoVisible ? 'Hide' : 'Show'}
|
||||
preset='secondary'
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
tx="onDeviceInfo"
|
||||
RightComponent={<View style={$rightContainer}>
|
||||
<Button
|
||||
onPress={toggleLocalInfo}
|
||||
text={isLocalInfoVisible ? 'Hide' : 'Show'}
|
||||
preset='secondary'
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
{isLocalInfoVisible && (
|
||||
<JSONTree
|
||||
|
||||
@@ -97,7 +97,7 @@ export const MintBalanceSelector = observer(function (props: {
|
||||
ListFooterComponent={props.mintBalances.length > 1 ? (
|
||||
<View>
|
||||
<Button
|
||||
text={`${allVisible ? 'Hide' : 'Show'} more`}
|
||||
tx={allVisible ? 'common.hideMore' : 'common.showMore'}
|
||||
onPress={toggleAllVisible}
|
||||
preset='tertiary'
|
||||
/>
|
||||
|
||||
+25
-27
@@ -117,14 +117,13 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
if (!selectedMint) {return}
|
||||
|
||||
try {
|
||||
setIsLoading(true)
|
||||
await mintsStore.updateMint(selectedMint.mintUrl)
|
||||
setInfo('Mint settings have been updated')
|
||||
setIsLoading(true)
|
||||
await mintsStore.updateMint(selectedMint.mintUrl)
|
||||
setInfo(translate("mintSettingsUpdated"))
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
} finally {
|
||||
|
||||
setIsLoading(false)
|
||||
handleError(e)
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,24 +142,23 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
let message: string = ''
|
||||
|
||||
if (proofsByMint && proofsByMint.length > 0) {
|
||||
message = `Your wallet has non-zero balance with this mint. If removed, your ecash will be lost!\n\n`
|
||||
message = translate("removingMintLostBalanceWarning") + `\n\n`
|
||||
}
|
||||
|
||||
message += `Do you really want to remove ${selectedMint.hostname} - ${selectedMint.shortname} from the wallet?`
|
||||
message += translate("confirmMintRemoval", { hostname: selectedMint.hostname, shortname: selectedMint.shortname })
|
||||
|
||||
Alert.alert(
|
||||
'Warning',
|
||||
translate("warning"),
|
||||
message,
|
||||
[
|
||||
{
|
||||
text: 'Cancel',
|
||||
text: translate('common.cancel'),
|
||||
style: 'cancel',
|
||||
onPress: () => {
|
||||
// Action canceled
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Confirm',
|
||||
text: translate('common.confirm'),
|
||||
onPress: () => {
|
||||
try {
|
||||
onMintUnselect()
|
||||
@@ -234,7 +232,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
return (
|
||||
<Screen preset="auto" contentContainerStyle={$screen}>
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text preset="heading" text="Manage mints" style={{color: 'white'}} />
|
||||
<Text preset="heading" tx="manageMints" style={{color: 'white'}} />
|
||||
</View>
|
||||
<View style={$contentContainer}>
|
||||
<Card
|
||||
@@ -242,7 +240,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text={'Add mint'}
|
||||
tx='mintsScreen.addMint'
|
||||
LeftComponent={<Icon
|
||||
containerStyle={$iconContainer}
|
||||
icon="faPlus"
|
||||
@@ -255,7 +253,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
/>
|
||||
{!mintsStore.alreadyExists(defaultMintUrl) && (
|
||||
<ListItem
|
||||
text={'Add Minibits mint'}
|
||||
tx="mintsScreen.addMintMinibits"
|
||||
LeftComponent={<SvgXml
|
||||
width={spacing.medium}
|
||||
height={spacing.medium}
|
||||
@@ -303,14 +301,14 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
<ListItem
|
||||
leftIcon="faInfoCircle"
|
||||
onPress={gotoInfo}
|
||||
tx={'mintsScreen.mintInfo'}
|
||||
tx='mintsScreen.mintInfo'
|
||||
bottomSeparator={true}
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
<ListItem
|
||||
leftIcon='faRotate'
|
||||
onPress={updateMint}
|
||||
text={'Refresh mint settings'}
|
||||
tx="mintsScreen.refreshMintSettings"
|
||||
bottomSeparator={true}
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
@@ -318,7 +316,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
<ListItem
|
||||
leftIcon="faShieldHalved"
|
||||
onPress={unblockMint}
|
||||
tx={'mintsScreen.unblockMint'}
|
||||
tx='mintsScreen.unblockMint'
|
||||
bottomSeparator={true}
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
@@ -326,7 +324,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
<ListItem
|
||||
leftIcon="faShieldHalved"
|
||||
onPress={blockMint}
|
||||
tx={'mintsScreen.blockMint'}
|
||||
tx='mintsScreen.blockMint'
|
||||
bottomSeparator={true}
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
@@ -341,14 +339,14 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
<ListItem
|
||||
leftIcon="faCopy"
|
||||
onPress={onCopyMintUrl}
|
||||
tx={'mintsScreen.copy'}
|
||||
tx='mintsScreen.copy'
|
||||
bottomSeparator={true}
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
<ListItem
|
||||
leftIcon="faXmark"
|
||||
onPress={removeMint}
|
||||
tx={'mintsScreen.removeMint'}
|
||||
tx='mintsScreen.removeMint'
|
||||
bottomSeparator={true}
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
@@ -363,7 +361,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
<View style={$bottomModal}>
|
||||
<Text
|
||||
preset="subheading"
|
||||
tx={'mintsScreen.addMintUrl'}
|
||||
tx='mintsScreen.addMintUrl'
|
||||
// style={{marginBottom: spacing.medium, textAlign: 'center'}}
|
||||
/>
|
||||
<View style={{flexDirection: 'row', alignItems: 'center', marginTop: spacing.small}}>
|
||||
@@ -380,7 +378,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
/>
|
||||
<Button
|
||||
preset='secondary'
|
||||
text="Paste"
|
||||
tx='common.paste'
|
||||
style={{
|
||||
borderRadius: 0,
|
||||
marginLeft: 1,
|
||||
@@ -389,7 +387,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
/>
|
||||
<Button
|
||||
preset='secondary'
|
||||
text="Scan"
|
||||
tx="common.scan"
|
||||
style={{
|
||||
borderTopLeftRadius: 0,
|
||||
borderBottomLeftRadius: 0,
|
||||
@@ -400,7 +398,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
</View>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
text="Save"
|
||||
tx='common.save'
|
||||
style={{
|
||||
// borderTopLeftRadius: 0,
|
||||
// borderBottomLeftRadius: 0,
|
||||
@@ -410,7 +408,7 @@ export const MintsScreen: FC<SettingsStackScreenProps<'Mints'>> = observer(funct
|
||||
onPress={addMint}
|
||||
/>
|
||||
<Button
|
||||
tx={'common.cancel'}
|
||||
tx='common.cancel'
|
||||
onPress={toggleAddMintModal}
|
||||
preset="secondary"
|
||||
/>
|
||||
|
||||
@@ -13,6 +13,7 @@ import { log } from '../services/logService'
|
||||
import { KeyChain, KeyPair, NostrClient, NostrProfile } from '../services'
|
||||
import { MINIBITS_NIP05_DOMAIN } from '@env'
|
||||
import { ProfileHeader } from './Contacts/ProfileHeader'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
|
||||
if (Platform.OS === 'android' &&
|
||||
@@ -63,7 +64,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
const onPasteOwnNip05 = async function () {
|
||||
const nip = await Clipboard.getString()
|
||||
if (!nip) {
|
||||
setInfo('Copy your NOSTR address first, then paste.')
|
||||
setInfo(translate("nostr.pasteError"))
|
||||
return
|
||||
}
|
||||
setOwnNip05(nip)
|
||||
@@ -76,12 +77,12 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
const nip05Domain = NostrClient.getDomainFromNip05(ownNip05)
|
||||
|
||||
if(!nip05Name || !nip05Domain) {
|
||||
setInfo(`Invalid NOSTR address, please check that it follows name@domain.com format`)
|
||||
setInfo(translate("nostr.invalidAddressFormat"))
|
||||
}
|
||||
|
||||
if(MINIBITS_NIP05_DOMAIN.includes(nip05Domain as string)) {
|
||||
setInfo(`${MINIBITS_NIP05_DOMAIN} names and keys can't be used with multiple wallets.`)
|
||||
return
|
||||
setInfo(translate("nostr.minibitsNameKeyReuseError", { domain: MINIBITS_NIP05_DOMAIN }))
|
||||
return
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
@@ -109,24 +110,34 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
const profile: NostrProfile | undefined = await NostrClient.getProfileFromRelays(nip05Pubkey, relaysToConnect)
|
||||
|
||||
if(!profile) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Could not retrieve profile from relays', {nip05Pubkey, relaysToConnect})
|
||||
throw new AppError(Err.VALIDATION_ERROR, "Could not retrieve profile from relays", {
|
||||
nip05Pubkey, relaysToConnect
|
||||
})
|
||||
}
|
||||
|
||||
// check that the profile's nip05 matches the one given by user and living on nip05 .well-known server
|
||||
if(!profile.nip05) {
|
||||
if(profile.name && profile.name.toLowerCase() === nip05Name) {
|
||||
profile.nip05 = ownNip05
|
||||
profile.nip05 = ownNip05
|
||||
} else {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Profile from the relay does not match the given nip05 identifier', {ownNip05, profile})
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
translate("nostr.profileRelayMismatchedIdentifierError"),
|
||||
{ ownNip05, profile }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if(profile.nip05 !== ownNip05) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Profile from the relay does not match the given nip05 identifier', {ownNip05, profile})
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
translate("nostr.profileRelayMismatchedIdentifierError"),
|
||||
{ ownNip05, profile }
|
||||
)
|
||||
}
|
||||
|
||||
if(!profile.name) {
|
||||
profile.name = nip05Name as string
|
||||
profile.name = nip05Name as string
|
||||
}
|
||||
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
@@ -144,7 +155,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
const onPasteOwnNsec = async function () {
|
||||
const key = await Clipboard.getString()
|
||||
if (!key) {
|
||||
setInfo('Copy your nsec key first, then paste.')
|
||||
setInfo(translate("nsecPasteError"))
|
||||
return
|
||||
}
|
||||
setOwnNsec(key)
|
||||
@@ -154,14 +165,14 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
const onConfirmOwnNsec = async function () {
|
||||
try {
|
||||
if(!ownProfile) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing profile to update')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("nsecMissingProfileError"))
|
||||
}
|
||||
// validate that nsec matches profile pubkey
|
||||
const privateKey = NostrClient.getHexkey(ownNsec)
|
||||
const publicKey = getPublicKey(privateKey)
|
||||
|
||||
if(publicKey !== ownProfile.pubkey) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Provided private key does not match your new profile public key.', {publicKey})
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("nsecPrivatePublicKeyMismatchError"), {publicKey})
|
||||
}
|
||||
|
||||
setOwnKeyPair({publicKey, privateKey})
|
||||
@@ -175,7 +186,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
const onConfirmChange = async function () {
|
||||
try {
|
||||
if(!ownProfile || !ownKeyPair) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing profile to update')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate('nsecMissingProfileError'))
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
@@ -227,36 +238,40 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
{!ownProfile ? (
|
||||
<View style={$nip05Container}>
|
||||
<ListItem
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: iconNip05}]}><Text text='1'/></View>}
|
||||
text='Enter your Nostr address'
|
||||
subText={'Minibits uses Nostr address (nip05) as a sharable contact to send and receive ecash.'}
|
||||
bottomSeparator={true}
|
||||
style={{}}
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: iconNip05}]}><Text text='1'/></View>}
|
||||
tx="nostr.enterAddress"
|
||||
subTx="nostr.enterAddressDesc"
|
||||
bottomSeparator={true}
|
||||
style={{}}
|
||||
/>
|
||||
<View style={{flexDirection: 'row', alignItems: 'center', marginVertical: spacing.medium}}>
|
||||
<View style={{
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
marginVertical: spacing.medium
|
||||
}}>
|
||||
<TextInput
|
||||
ref={ownNip05InputRef}
|
||||
onChangeText={(name) => setOwnNip05(name.trim())}
|
||||
value={ownNip05}
|
||||
autoCapitalize='none'
|
||||
keyboardType='default'
|
||||
maxLength={30}
|
||||
placeholder='name@domain.com'
|
||||
selectTextOnFocus={true}
|
||||
style={[$nip05Input, {backgroundColor: inputBg}]}
|
||||
ref={ownNip05InputRef}
|
||||
onChangeText={(name) => setOwnNip05(name.trim())}
|
||||
value={ownNip05}
|
||||
autoCapitalize='none'
|
||||
keyboardType='default'
|
||||
maxLength={30}
|
||||
placeholder='name@domain.com'
|
||||
selectTextOnFocus={true}
|
||||
style={[$nip05Input, {backgroundColor: inputBg}]}
|
||||
/>
|
||||
<Button
|
||||
tx={'common.paste'}
|
||||
preset='secondary'
|
||||
style={$pasteButton}
|
||||
onPress={onPasteOwnNip05}
|
||||
tx='common.paste'
|
||||
preset='secondary'
|
||||
style={$pasteButton}
|
||||
onPress={onPasteOwnNip05}
|
||||
/>
|
||||
<Button
|
||||
tx={'common.confirm'}
|
||||
style={$saveButton}
|
||||
onPress={onConfirmOwnNip05}
|
||||
tx='common.confirm'
|
||||
style={$saveButton}
|
||||
onPress={onConfirmOwnNip05}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : (
|
||||
<>
|
||||
@@ -278,7 +293,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
{ownProfileRelays.length > 0 && (
|
||||
<ListItem
|
||||
leftIcon='faCircleNodes'
|
||||
text='Relays'
|
||||
tx='relays'
|
||||
subText={ownProfileRelays.toString()}
|
||||
topSeparator={true}
|
||||
style={{}}
|
||||
@@ -287,7 +302,7 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
{isSetupCompleted && (
|
||||
<ListItem
|
||||
leftIcon='faKey'
|
||||
text='Private key'
|
||||
tx='privateKey'
|
||||
subText={ownNsec}
|
||||
topSeparator={true}
|
||||
style={{}}
|
||||
@@ -305,8 +320,8 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
<View style={$nip05Container}>
|
||||
<ListItem
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: iconNip05}]}><Text text='2'/></View>}
|
||||
text='Enter your private key'
|
||||
subText={'Minibits needs your private key in nsec format in order to decrypt messages containing incoming payments. Your key will be stored in your device secure key vault.'}
|
||||
tx="nsecEnterPrivateKey"
|
||||
subTx="nsecEnterPrivateKeyDesc"
|
||||
bottomSeparator={true}
|
||||
style={{}}
|
||||
/>
|
||||
@@ -324,13 +339,13 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
style={[$nip05Input, {backgroundColor: inputBg}]}
|
||||
/>
|
||||
<Button
|
||||
tx={'common.paste'}
|
||||
tx='common.paste'
|
||||
preset='secondary'
|
||||
style={$pasteButton}
|
||||
onPress={onPasteOwnNsec}
|
||||
/>
|
||||
<Button
|
||||
tx={'common.confirm'}
|
||||
tx='common.confirm'
|
||||
style={$saveButton}
|
||||
onPress={onConfirmOwnNsec}
|
||||
/>
|
||||
@@ -348,14 +363,14 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
<>
|
||||
<ListItem
|
||||
leftIcon='faTriangleExclamation'
|
||||
text='Consider before change'
|
||||
subText={'Using your own Nostr address will disable Lightning address features unique to minibits.cash address.'}
|
||||
tx="nostr.lastStandDialog.title"
|
||||
subTx="nostr.lastStandDialog.desc"
|
||||
/>
|
||||
<ListItem
|
||||
leftIcon='faCheckCircle'
|
||||
leftIconColor={colors.palette.success200}
|
||||
text='Profile change is ready'
|
||||
subText='Wallet needs to restart to apply this change.'
|
||||
tx="nostr.lastStandDialog.readyTitle"
|
||||
subText="nostr.lastStandDialog.readyDesc"
|
||||
style={{}}
|
||||
/>
|
||||
</>
|
||||
@@ -364,12 +379,12 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="default"
|
||||
text={'Apply change'}
|
||||
tx='nostr.lastStandDialog.confirm'
|
||||
onPress={onConfirmChange}
|
||||
/>
|
||||
<Button
|
||||
preset="secondary"
|
||||
text={'Save my soul'}
|
||||
tx='nostr.lastStandDialog.cancel'
|
||||
onPress={onCancelChange}
|
||||
/>
|
||||
</View>
|
||||
@@ -385,12 +400,12 @@ export const OwnKeysScreen: FC<OwnKeysScreenProps> = observer(function OwnKeysSc
|
||||
<ProfileHeader headerBg='transparent' />
|
||||
<Text
|
||||
style={{color: textResult, textAlign: 'center', marginTop: spacing.small}}
|
||||
text={'Profile change is complete.'}
|
||||
tx="nostr.lastStandDialog.complete"
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={() => {
|
||||
navigation.navigate('Contacts', {})
|
||||
}}
|
||||
|
||||
@@ -74,11 +74,11 @@ export const OutgoingRequests = observer(function (props: {
|
||||
|
||||
if (result.action === Share.sharedAction) {
|
||||
setTimeout(
|
||||
() => infoMessage(translate("paymentRequestScreen.outgoing.invoiceShared")),
|
||||
() => infoMessage(translate("lightningInvoiceSharedWaiting")),
|
||||
500,
|
||||
)
|
||||
} else if (result.action === Share.dismissedAction) {
|
||||
infoMessage(translate("paymentRequestScreen.outgoing.sharingCancelled"))
|
||||
infoMessage(translate("share.cancelled"))
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
@@ -90,7 +90,7 @@ export const OutgoingRequests = observer(function (props: {
|
||||
try {
|
||||
Clipboard.setString(selectedRequest?.encodedInvoice as string)
|
||||
} catch (e: any) {
|
||||
setInfo(translate("common.copyFailParam", { param: e.message }))
|
||||
setInfo(translate('common.copyFailParam', { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ export const PaymentRequestListItem = observer(function (
|
||||
{pr.type === PaymentRequestType.INCOMING ? (
|
||||
<>
|
||||
<Text
|
||||
tx={'paymentCommon.pay'}
|
||||
tx={'payCommon.pay'}
|
||||
style={{marginBottom: spacing.small}}
|
||||
/>
|
||||
<CurrencyAmount
|
||||
@@ -97,7 +97,7 @@ export const PaymentRequestListItem = observer(function (
|
||||
) : (
|
||||
<>
|
||||
<Text
|
||||
tx={'paymentCommon.payMe'}
|
||||
tx={'payCommon.payMe'}
|
||||
style={{marginBottom: spacing.small}}
|
||||
/>
|
||||
<CurrencyAmount
|
||||
@@ -160,7 +160,7 @@ export const PaymentRequestListItem = observer(function (
|
||||
<Text
|
||||
size="xxs"
|
||||
style={{color: dim, marginBottom: -10}}
|
||||
text={translate('paymentCommon.pay')}
|
||||
text={translate('payCommon.pay')}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
@@ -259,8 +259,8 @@ export const PaymentRequestListItem = observer(function (
|
||||
preset="secondary"
|
||||
tx={
|
||||
pr.type === PaymentRequestType.INCOMING
|
||||
? "paymentCommon.payFromWallet"
|
||||
: 'paymentCommon.receiveInPerson'
|
||||
? "payCommon.payFromWallet"
|
||||
: 'payCommon.receiveInPerson'
|
||||
}
|
||||
onPress={onPressPaymentRequest}
|
||||
/>
|
||||
|
||||
@@ -50,7 +50,7 @@ export const PaymentRequestsScreen: FC<PaymentRequestsScreenProps> = observer(fu
|
||||
return (
|
||||
<Screen contentContainerStyle={$screen}>
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text preset="heading" text="Payment requests" style={{color: 'white'}} />
|
||||
<Text preset="heading" tx="payCommon.paymentRequests" style={{color: 'white'}} />
|
||||
</View>
|
||||
<TabView
|
||||
renderTabBar={renderTabBar}
|
||||
|
||||
@@ -92,7 +92,7 @@ export const PictureScreen: FC<PictureScreenProps> = observer(function PictureSc
|
||||
<Card
|
||||
ContentComponent={<ListItem
|
||||
leftIcon='faXmark'
|
||||
text='Could not retrieve pictures from the server, try again later.'
|
||||
tx="pictureRetrieveFail"
|
||||
/>}
|
||||
/>
|
||||
)}
|
||||
@@ -101,12 +101,12 @@ export const PictureScreen: FC<PictureScreenProps> = observer(function PictureSc
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="default"
|
||||
tx={'common.confirm'}
|
||||
tx='common.confirm'
|
||||
onPress={onPictureConfirm}
|
||||
/>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.cancel'}
|
||||
tx='common.cancel'
|
||||
onPress={() => setSelectedPicture('')}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { log } from '../services/logService'
|
||||
import { OwnKeysScreen } from './OwnKeysScreen'
|
||||
import { MINIBITS_NIP05_DOMAIN } from '@env'
|
||||
import { CommonActions, StackActions } from '@react-navigation/native'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
enum TorStatus {
|
||||
NOTINIT = 'NOTINIT',
|
||||
@@ -305,8 +306,11 @@ export const PrivacyScreen: FC<SettingsStackScreenProps<'Privacy'>> = observer(f
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text="Use own Nostr profile"
|
||||
subText={walletProfileStore.isOwnProfile ? walletProfileStore.nip05 : "Import your own Nostr address and keys. Your wallet will stop communicating with minibits.cash Nostr and LNURL address servers, disabling Lightning address features for receiving zaps and payments. Only for hardcore ecash-ers!"}
|
||||
tx="nostr.useOwnProfile.title"
|
||||
subText={walletProfileStore.isOwnProfile
|
||||
? walletProfileStore.nip05
|
||||
: translate("nostr.useOwnProfile.desc")
|
||||
}
|
||||
leftIcon={'faShareNodes'}
|
||||
leftIconColor={
|
||||
walletProfileStore.isOwnProfile
|
||||
@@ -320,14 +324,14 @@ export const PrivacyScreen: FC<SettingsStackScreenProps<'Privacy'>> = observer(f
|
||||
<Button
|
||||
style={{maxHeight: 10, marginTop: spacing.medium}}
|
||||
preset="secondary"
|
||||
text="Reset"
|
||||
tx="common.reset"
|
||||
onPress={resetProfile}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
style={{maxHeight: 10, marginTop: spacing.medium}}
|
||||
preset="secondary"
|
||||
text="Import"
|
||||
tx="common.import"
|
||||
onPress={gotoOwnKeys}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { log } from '../services/logService'
|
||||
import { KeyChain, MinibitsClient, NostrClient, NostrProfile } from '../services'
|
||||
import { MINIBITS_NIP05_DOMAIN } from '@env'
|
||||
import { StackActions } from '@react-navigation/native'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
interface ProfileScreenProps extends ContactsStackScreenProps<'Profile'> {}
|
||||
|
||||
@@ -93,17 +94,17 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
|
||||
|
||||
const onCopyNpub = function () {
|
||||
try {
|
||||
Clipboard.setString(npub)
|
||||
Clipboard.setString(npub)
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not copy: ${e.message}`)
|
||||
setInfo(translate('common.copyFailParam', { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
const onCopyNip05 = function () {
|
||||
try {
|
||||
Clipboard.setString(nip05)
|
||||
Clipboard.setString(nip05)
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not copy: ${e.message}`)
|
||||
setInfo(translate('common.copyFailParam', { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,13 +115,21 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
|
||||
toggleUpdateModal()
|
||||
|
||||
if(!walletProfileStore.nip05) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing address', {caller: 'onSyncOwnProfile'})
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
translate("profileMissingAddressError"),
|
||||
{ caller: 'onSyncOwnProfile' }
|
||||
)
|
||||
}
|
||||
|
||||
const profile: NostrProfile = await NostrClient.getNormalizedNostrProfile(walletProfileStore.nip05, relaysStore.allUrls)
|
||||
|
||||
if(profile.pubkey !== walletProfileStore.pubkey) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Profile from relays public key differs from yor wallet pubkey. Reset this profile in Privacy settings and import again with new keys.', {caller: 'onSyncOwnProfile', profile, pubkey: walletProfileStore.pubkey})
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
translate("profilePublicKeyMismatchError"),
|
||||
{caller: 'onSyncOwnProfile', profile, pubkey: walletProfileStore.pubkey}
|
||||
)
|
||||
}
|
||||
|
||||
log.trace('[onSyncOwnProfile]', {profile})
|
||||
@@ -133,7 +142,7 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
|
||||
})
|
||||
|
||||
setIsLoading(false)
|
||||
setInfo('Sync completed')
|
||||
setInfo(translate('syncCompleted'))
|
||||
return
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
@@ -211,13 +220,13 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
|
||||
{!walletProfileStore.pubkey || !walletProfileStore.picture ? (
|
||||
<>
|
||||
<ListItem
|
||||
text='Create wallet address'
|
||||
subText='Your minibits.cash wallet address allows you to receive encrypted ecash over Nostr. Simultaneously, it serves as your Lightning address, enabling you to receive payments from any Lightning wallet or zaps on the Nostr social network.'
|
||||
tx="profileOnboarding.title"
|
||||
subTx="profileOnboarding.desc"
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset='secondary'
|
||||
text={'Create'}
|
||||
tx="common.create"
|
||||
LeftAccessory={() => <Icon icon='faCircleUser'/>}
|
||||
onPress={createProfileIfNotExists}
|
||||
/>
|
||||
@@ -227,16 +236,16 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
|
||||
<>
|
||||
{walletProfileStore.isOwnProfile ? (
|
||||
<ListItem
|
||||
text='Your own wallet address'
|
||||
subText={`You are using your own Nostr address to send and receive ecash. Please note that this setup does not allow you to receive Nostr zaps or Lightning payments to this address.`}
|
||||
tx="profileOnboarding.ownAddrTitle"
|
||||
subTx="profileOnboarding.ownAddrDesc"
|
||||
leftIcon='faCircleUser'
|
||||
bottomSeparator={true}
|
||||
style={{paddingRight: spacing.small}}
|
||||
/>
|
||||
) : (
|
||||
<ListItem
|
||||
text='Your Minibits wallet address'
|
||||
subText={`Share your wallet address to receive encrypted ecash over Nostr. This also serves as your Lightning address, enabling you to receive payments from any Lightning wallet or zaps on the Nostr social network.`}
|
||||
tx="profileOnboarding.minibitsTitle"
|
||||
subTx="profileOnboarding.minibitsDesc"
|
||||
leftIcon='faCircleUser'
|
||||
bottomSeparator={true}
|
||||
style={{paddingRight: spacing.small}}
|
||||
@@ -245,13 +254,13 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset='secondary'
|
||||
text={'Share'}
|
||||
tx='common.share'
|
||||
LeftAccessory={() => <Icon icon='faShareNodes'/>}
|
||||
onPress={toggleShareModal}
|
||||
/>
|
||||
<Button
|
||||
preset='secondary'
|
||||
text={'Change'}
|
||||
tx="common.change"
|
||||
style={{marginLeft: spacing.small}}
|
||||
LeftAccessory={() => <Icon icon='faRotate'/>}
|
||||
onPress={toggleUpdateModal}
|
||||
@@ -277,15 +286,15 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
|
||||
{walletProfileStore.isOwnProfile && (
|
||||
<>
|
||||
<ListItem
|
||||
text='Sync own profile'
|
||||
subText='Synchronize your profile name and picture with up to date information from Nostr relays.'
|
||||
tx="syncOwnProfile"
|
||||
subTx="syncOwnProfileDesc"
|
||||
leftIcon='faRotate'
|
||||
onPress={onSyncOwnProfile}
|
||||
bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
text='Reset own profile'
|
||||
subText='Stop using your own NOSTR address and re-create Minibits wallet profile.'
|
||||
tx="resetOwnProfile"
|
||||
subTx="resetOwnProfileDesc"
|
||||
leftIcon='faXmark'
|
||||
onPress={gotoPrivacy}
|
||||
/>
|
||||
@@ -302,14 +311,14 @@ export const ProfileScreen: FC<ProfileScreenProps> = observer(
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text='Share wallet address'
|
||||
tx="shareWalletAddress"
|
||||
subText={nip05}
|
||||
leftIcon='faShareNodes'
|
||||
onPress={onShareContact}
|
||||
bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
text='Copy Nostr public key'
|
||||
tx="nostr.copyPubKey"
|
||||
subText={npub}
|
||||
leftIcon='faCopy'
|
||||
onPress={onCopyNpub}
|
||||
|
||||
@@ -50,14 +50,12 @@ export const ReceiveOptionsScreen: FC<WalletStackScreenProps<'ReceiveOptions'>>
|
||||
|
||||
|
||||
const gotoTopup = function () {
|
||||
navigation.navigate('Topup', {
|
||||
paymentOption: ReceiveOption.SHOW_INVOICE}
|
||||
)
|
||||
navigation.navigate('Topup', { paymentOption: ReceiveOption.SHOW_INVOICE })
|
||||
}
|
||||
|
||||
|
||||
const onScan = async function () {
|
||||
navigation.navigate('Scan')
|
||||
navigation.navigate('Scan')
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ import { MintHeader } from './Mints/MintHeader'
|
||||
import { round, toNumber } from '../utils/number'
|
||||
import numbro from 'numbro'
|
||||
import { TranItem } from './TranDetailScreen'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
function ReceiveScreen({route, navigation}) {
|
||||
@@ -91,14 +92,14 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
|
||||
if (error) {
|
||||
setResultModalInfo({
|
||||
status,
|
||||
title: error.params?.message ? error.message : 'Receive failed',
|
||||
message: error.params?.message || error.message,
|
||||
status,
|
||||
title: error.params?.message ? error.message : translate("transactionCommon.receiveFailed"),
|
||||
message: error.params?.message || error.message,
|
||||
})
|
||||
} else {
|
||||
setResultModalInfo({
|
||||
status,
|
||||
message,
|
||||
status,
|
||||
message,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -151,10 +152,8 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
log.trace('decoded token', {decoded})
|
||||
log.trace('tokenAmounts', {tokenAmounts})
|
||||
|
||||
|
||||
|
||||
if(!decoded.unit) {
|
||||
setInfo(`Currency unit is missing in the received token. Wallet will assume token amount is in bitcoin ${CurrencyCode.SATS}. Do not continue if you are not sure this is correct.`)
|
||||
setInfo(translate("decodedMissingCurrencyUnit", { unit: CurrencyCode.SATS }))
|
||||
}
|
||||
|
||||
const currency = getCurrency(decoded.unit)
|
||||
@@ -203,8 +202,6 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
memo,
|
||||
encodedToken as string,
|
||||
)
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -293,7 +290,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
key={mintUrl}
|
||||
text={new URL(mintUrl).hostname}
|
||||
topSeparator={true}
|
||||
RightComponent={<Text size='xs' text='New mint' style={$newBadge}/>}
|
||||
RightComponent={<Text size='xs' tx="newMint" style={$newBadge}/>}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
@@ -318,13 +315,13 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
ContentComponent={
|
||||
<>
|
||||
<TranItem
|
||||
label="tranDetailScreen.receivedTo"
|
||||
label="transactionCommon.receivedTo"
|
||||
isFirst={true}
|
||||
value={mintsStore.findByUrl(transaction.mint)?.shortname as string}
|
||||
/>
|
||||
{transaction?.memo && (
|
||||
<TranItem
|
||||
label="tranDetailScreen.memoFromSender"
|
||||
label="receiveScreen.memoFromSender"
|
||||
value={transaction.memo as string}
|
||||
/>
|
||||
)}
|
||||
@@ -335,7 +332,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
isCurrency={true}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.status"
|
||||
label="receiveScreen.status"
|
||||
value={transaction.status as string}
|
||||
/>
|
||||
</>
|
||||
@@ -357,7 +354,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
<View style={$buttonContainer}>
|
||||
{isInternetReachable ? (
|
||||
<Button
|
||||
tx={'receiveScreen.receive'}
|
||||
tx='payCommon.receive'
|
||||
onPress={receiveToken}
|
||||
style={{marginRight: spacing.medium}}
|
||||
LeftAccessory={() => (
|
||||
@@ -370,7 +367,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
tx={'receiveScreen.receiveOffline'}
|
||||
tx='payCommon.receiveOffline'
|
||||
onPress={receiveOfflineToken}
|
||||
style={{marginRight: spacing.medium}}
|
||||
LeftAccessory={() => (
|
||||
@@ -384,7 +381,7 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
)}
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.cancel'}
|
||||
tx='common.cancel'
|
||||
onPress={gotoWallet}
|
||||
/>
|
||||
</View>
|
||||
@@ -405,13 +402,13 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
<ResultModalInfo
|
||||
icon={'faCheckCircle'}
|
||||
iconColor={colors.palette.success200}
|
||||
title="Success!"
|
||||
title={translate('common.success')}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={gotoWallet}
|
||||
/>
|
||||
</View>
|
||||
@@ -422,13 +419,13 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
<ResultModalInfo
|
||||
icon={'faTriangleExclamation'}
|
||||
iconColor={colors.palette.accent400}
|
||||
title="Attention!"
|
||||
title={translate('attention')}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={gotoWallet}
|
||||
/>
|
||||
</View>
|
||||
@@ -440,13 +437,13 @@ export const ReceiveScreen: FC<WalletStackScreenProps<'Receive'>> = observer(
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.angry500}
|
||||
title={resultModalInfo?.title as string || 'Receive failed'}
|
||||
title={resultModalInfo?.title as string || translate('transactionCommon.receiveFailed')}
|
||||
message={resultModalInfo?.message as string}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={toggleResultModal}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -12,6 +12,7 @@ import { Relay } from '../models/Relay'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { log, WalletTask } from '../services'
|
||||
import { moderateVerticalScale, verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
interface SettingsScreenProps extends SettingsStackScreenProps<'Relays'> {}
|
||||
|
||||
@@ -70,7 +71,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
|
||||
const onPastePublicRelay = async function () {
|
||||
const url = await Clipboard.getString()
|
||||
if (!url) {
|
||||
setInfo('Copy your relay URL key first, then paste')
|
||||
setInfo(translate('relayurlPasteError'))
|
||||
return
|
||||
}
|
||||
setNewPublicRelay(url)
|
||||
@@ -81,22 +82,26 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
|
||||
try {
|
||||
if(newPublicRelay && newPublicRelay.startsWith('wss://')) {
|
||||
if(relaysStore.alreadyExists(newPublicRelay)) {
|
||||
setInfo('Relay already exists.')
|
||||
return
|
||||
setInfo(translate('relayExists'))
|
||||
return
|
||||
}
|
||||
|
||||
relaysStore.addRelay({
|
||||
url: newPublicRelay,
|
||||
status: WebSocket.CLOSED
|
||||
url: newPublicRelay,
|
||||
status: WebSocket.CLOSED
|
||||
})
|
||||
|
||||
toggleAddRelayModal()
|
||||
onConnect()
|
||||
} else {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Invalid relay URL.', newPublicRelay)
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
translate("invalidRelayUrl"),
|
||||
newPublicRelay
|
||||
)
|
||||
}
|
||||
} catch(e: any) {
|
||||
handleError(e)
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +109,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
|
||||
const resetDefaultRelays = function () {
|
||||
try {
|
||||
for (const relay of relaysStore.allPublicRelays) {
|
||||
relaysStore.removeRelay(relay.url)
|
||||
relaysStore.removeRelay(relay.url)
|
||||
}
|
||||
|
||||
relaysStore.addDefaultRelays()
|
||||
@@ -205,7 +210,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
|
||||
<ListItem
|
||||
leftIcon="faCloudArrowUp"
|
||||
onPress={onConnect}
|
||||
text={'Reconnect'}
|
||||
tx="common.reconnect"
|
||||
bottomSeparator={true}
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
@@ -213,7 +218,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
|
||||
<ListItem
|
||||
leftIcon="faXmark"
|
||||
onPress={removeRelay}
|
||||
text={'Remove relay'}
|
||||
tx="removeRelay"
|
||||
style={{paddingHorizontal: spacing.medium}}
|
||||
/>
|
||||
</View>
|
||||
@@ -236,7 +241,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
|
||||
maxLength={64}
|
||||
placeholder='wss://...'
|
||||
selectTextOnFocus={true}
|
||||
style={[$relayInput, {backgroundColor: inputBg}]}
|
||||
style={[$relayInput, {backgroundColor: inputBg}]}
|
||||
/>
|
||||
<Button
|
||||
tx={'common.paste'}
|
||||
@@ -251,7 +256,7 @@ export const RelaysScreen: FC<SettingsScreenProps> = observer(
|
||||
/>
|
||||
</View>
|
||||
<View style={[$buttonContainer, {marginTop: spacing.medium}]}>
|
||||
<Button preset='tertiary' onPress={resetDefaultRelays} text='Reset default'/>
|
||||
<Button preset='tertiary' onPress={resetDefaultRelays} tx='common.resetDefault' />
|
||||
<Button preset='tertiary' onPress={toggleAddRelayModal} style={{marginLeft: spacing.small}} text='Cancel'/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
@@ -23,6 +23,7 @@ import AppError, { Err } from '../utils/AppError'
|
||||
import { KeyChain, log, MintClient } from '../services'
|
||||
import { scale } from '@gocodingnow/rn-size-matters'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
|
||||
export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> = observer(function RemoteBackupScreen(_props) {
|
||||
@@ -59,7 +60,10 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
|
||||
}
|
||||
|
||||
if (!validateMnemonic(mnemonic, wordlist)) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Corrupted or invalid mnemonic. Please move funds and reinstall this wallet.')
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
translate("backupScreen.invalidMnemonicError")
|
||||
)
|
||||
}
|
||||
|
||||
setMnemonic(mnemonic)
|
||||
@@ -79,14 +83,17 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
|
||||
Clipboard.setString(mnemonic)
|
||||
|
||||
if(isNewMnemonic) {
|
||||
setInfo('To apply backup to your existing funds, send your entire balance to yourself. Otherwise, only funds from this point onward can be restored.')
|
||||
setInfo(translate("copyMnemonicBackupWorkaround"))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic.')
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
translate("backupScreen.missingMnemonicError")
|
||||
)
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not copy: ${e.message}`)
|
||||
setInfo(translate('common.copyFailParam', { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,15 +107,15 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
|
||||
return (
|
||||
<Screen style={$screen}>
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text preset="heading" text="Seed backup" style={{color: 'white'}} />
|
||||
<Text preset="heading" tx='backupScreen.seedBackup' style={{color: 'white'}} />
|
||||
</View>
|
||||
<View style={$contentContainer}>
|
||||
<Card
|
||||
style={$card}
|
||||
ContentComponent={
|
||||
<ListItem
|
||||
text='Your mnemonic phrase'
|
||||
subText='This 12 word sequence represents your seed and allows you to recover your ecash balance in case of device loss. Keep them in safe place.'
|
||||
tx="backupScreen.mnemonicTitle"
|
||||
subTx="backupScreen.mnemonicDesc"
|
||||
leftIcon='faInfoCircle'
|
||||
leftIconColor={colors.palette.iconYellow300}
|
||||
leftIconInverse={true}
|
||||
@@ -147,7 +154,7 @@ export const RemoteBackupScreen: FC<SettingsStackScreenProps<'RemoteBackup'>> =
|
||||
<Button
|
||||
preset="default"
|
||||
style={{margin: spacing.small}}
|
||||
text="Copy"
|
||||
tx='common.copy'
|
||||
onPress={onCopy}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -44,10 +44,10 @@ import { WalletScreen } from './WalletScreen'
|
||||
import { MintUnit, formatCurrency, getCurrency } from '../services/wallet/currency'
|
||||
import { isObj } from '@cashu/cashu-ts/src/utils'
|
||||
import { WalletProfileRecord } from '../models/WalletProfileStore'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
if (Platform.OS === 'android' &&
|
||||
UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true)
|
||||
if (Platform.OS === 'android' && UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true)
|
||||
}
|
||||
|
||||
const RESTORE_INDEX_INTERVAL = 50
|
||||
@@ -126,7 +126,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
const maybeMnemonic = await Clipboard.getString()
|
||||
|
||||
if(!maybeMnemonic) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic phrase.')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate('backupScreen.missingMnemonicError'))
|
||||
}
|
||||
|
||||
const cleanedMnemonic = maybeMnemonic.replace(/\s+/g, ' ').trim()
|
||||
@@ -140,16 +140,16 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
|
||||
const onConfirm = async function () {
|
||||
try {
|
||||
setStatusMessage('Deriving seed, this takes a while...')
|
||||
setStatusMessage(translate("derivingSeedStatus"))
|
||||
|
||||
if(!mnemonic) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic.')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate('backupScreen.missingMnemonicError'))
|
||||
}
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setIsLoading(true)
|
||||
|
||||
if (!validateMnemonic(mnemonic, wordlist)) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Invalid mnemonic phrase. Provide 12 word sequence separated by blank spaces.')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("recoveryInvalidMnemonicError"))
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
@@ -160,7 +160,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
setIsLoading(false)
|
||||
}, 200)
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,10 +209,10 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
|
||||
const startRecovery = async function () {
|
||||
if(!selectedMintUrl) {
|
||||
setInfo('Select mint to recover from.')
|
||||
return
|
||||
setInfo(translate("recovery.selectMintFrom"))
|
||||
return
|
||||
}
|
||||
setStatusMessage('Starting recovery...')
|
||||
setStatusMessage(translate("recovery.starting"))
|
||||
setIsLoading(true)
|
||||
setTimeout(() => doRecovery(), 100)
|
||||
}
|
||||
@@ -231,16 +231,16 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
|
||||
try {
|
||||
if(!recoveredMint) {
|
||||
setInfo('Please select the mint.')
|
||||
return
|
||||
setInfo(translate("recovery.noMintSelected"))
|
||||
return
|
||||
}
|
||||
|
||||
if(!selectedKeyset) {
|
||||
setInfo('Please select keyset.')
|
||||
return
|
||||
setInfo(translate("recovery.noKeysetSelected"))
|
||||
return
|
||||
}
|
||||
|
||||
setStatusMessage(`Restoring from ${recoveredMint.hostname}...`)
|
||||
setStatusMessage(translate("recovery.restoringFromParam", { hostname: recoveredMint.hostname }))
|
||||
log.info('[restore]', `Restoring from ${recoveredMint.hostname}...`)
|
||||
|
||||
const { proofs } = await MintClient.restore(
|
||||
@@ -254,7 +254,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
)
|
||||
|
||||
log.debug('[restore]', `Restored proofs`, proofs.length)
|
||||
setStatusMessage(`Found ${proofs.length} proofs...`)
|
||||
setStatusMessage(translate("recovery.foundProofsAmount", { amount: proofs.length }))
|
||||
|
||||
if (proofs.length > 0) {
|
||||
// need to move counter by whole interval to avoid duplicate _B!!!
|
||||
@@ -267,7 +267,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
|
||||
log.debug('[restore]', `Spent and pending proofs`, {spent: spent.length, pending: pending.length})
|
||||
|
||||
setStatusMessage(`${spent.length} proofs were already spent...`)
|
||||
setStatusMessage(translate("recovery.spentProofsAmount", { amount: spent.length }))
|
||||
|
||||
const spentAmount = CashuUtils.getProofsAmount(spent as Proof[])
|
||||
alreadySpentAmount += spentAmount
|
||||
@@ -276,7 +276,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
|
||||
if(unspent && unspent.length > 0) {
|
||||
|
||||
setStatusMessage(`Completing recovery...`)
|
||||
setStatusMessage(translate("recovery.completing"))
|
||||
|
||||
const amount = CashuUtils.getProofsAmount(unspent as Proof[])
|
||||
recoveredAmount = amount
|
||||
@@ -340,7 +340,8 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
|
||||
if(pending && pending.length > 0) {
|
||||
|
||||
setStatusMessage(`Found ${pending.length} pending proofs...`)
|
||||
// setStatusMessage(`Found ${pending.length} pending proofs...`)
|
||||
setStatusMessage(translate("recovery.foundPendingProofsAmount", { amount: pending.length }))
|
||||
log.debug(`Found pending ecash with ${recoveredMint.hostname}...`)
|
||||
|
||||
const amount = CashuUtils.getProofsAmount(pending as Proof[])
|
||||
@@ -448,25 +449,28 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
const currency = getCurrency(selectedKeyset?.unit as MintUnit)
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.COMPLETED,
|
||||
message: `${formatCurrency(recoveredAmount, currency.code)} ${currency.code} were recovered into your wallet.`
|
||||
message: translate("recovery.recoveredResult", {
|
||||
formattedCurrency: formatCurrency(recoveredAmount, currency.code),
|
||||
code: currency.code
|
||||
})
|
||||
})
|
||||
} else {
|
||||
if(errors.length > 0) {
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
message: `Recovery ended with errors.`
|
||||
message: translate("recovery.resultErrors")
|
||||
})
|
||||
setRecoveryErrors(errors)
|
||||
} else {
|
||||
if(alreadySpentAmount > 0) {
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.EXPIRED,
|
||||
message: `Good news is that already spent Ecash has been found. Continue with next recovery interval.`
|
||||
message: translate("recovery.resultSpent")
|
||||
})
|
||||
} else {
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.EXPIRED,
|
||||
message: `Nothing has been found in this recovery interval.`
|
||||
message: translate("recovery.resultExpired")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -480,7 +484,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
const onFindWalletAddress = async () => {
|
||||
try {
|
||||
if(!seed || !mnemonic) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic or seed.')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("backupScreen.missingMnemonicOrSeedError"))
|
||||
}
|
||||
|
||||
const seedHash = QuickCrypto.createHash('sha256')
|
||||
@@ -490,7 +494,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
const currentSeedHash = await KeyChain.loadSeedHash()
|
||||
|
||||
if(currentSeedHash === seedHash) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Can not use seed from this device.')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("recovery.seedFromCurrentDeviceError"))
|
||||
}
|
||||
|
||||
const profile = await MinibitsClient.getWalletProfileBySeedHash(seedHash as string)
|
||||
@@ -502,11 +506,11 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
if(profile.nip05.includes(MINIBITS_NIP05_DOMAIN)) {
|
||||
setProfileToRecover(profile)
|
||||
} else {
|
||||
setInfo(`You used wallet address ${profile.nip05} with your own keys, import it again.`)
|
||||
setInfo(translate("recovery.ownKeysImportAgain", { addr: profile.nip05 }))
|
||||
await delay(5000)
|
||||
}
|
||||
} else {
|
||||
setInfo('Could not find wallet address linked to your seed on the server.')
|
||||
setInfo(translate("recovery.noWalletForSeedError"))
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
@@ -517,10 +521,10 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
const onCompleteAddress = async () => {
|
||||
try {
|
||||
if(!seed || !mnemonic || !profileToRecover) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic or seed or profile to recover.')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("recovery.missingMnemonicSeedProfileError"))
|
||||
}
|
||||
|
||||
setStatusMessage('Recovering wallet address...')
|
||||
setStatusMessage(translate("recovery.recoveringAddress"))
|
||||
setIsLoading(true)
|
||||
|
||||
const seedHash = QuickCrypto.createHash('sha256')
|
||||
@@ -539,7 +543,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
await KeyChain.saveSeed(seed as Uint8Array)
|
||||
|
||||
await delay(1000)
|
||||
setStatusMessage(`Recovery completed`)
|
||||
setStatusMessage(translate("recovery.completed"))
|
||||
await delay(2000)
|
||||
|
||||
|
||||
@@ -556,10 +560,10 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
const onComplete = async () => {
|
||||
try {
|
||||
if(!seed || !mnemonic) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mnemonic or seed.')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate('backupScreen.missingMnemonicOrSeedError'))
|
||||
}
|
||||
|
||||
setStatusMessage('Recovering wallet address...')
|
||||
setStatusMessage(translate('recovery.recoveringAddress'))
|
||||
setIsLoading(true)
|
||||
|
||||
await KeyChain.saveMnemonic(mnemonic)
|
||||
@@ -574,7 +578,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
// Skip external profiles beacause we do not control keys
|
||||
if(profileToRecover) {
|
||||
log.info('[onComplete] recovery', {profileToRecover})
|
||||
setStatusMessage(`Found ${profileToRecover.nip05}`)
|
||||
setStatusMessage(translate("recovery.foundAddrParam", { addr: profileToRecover.nip05 }))
|
||||
|
||||
if(profileToRecover.nip05.includes(MINIBITS_NIP05_DOMAIN)) {
|
||||
const {publicKey: newPublicKey} = await NostrClient.getOrCreateKeyPair()
|
||||
@@ -583,11 +587,11 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
// Align walletId in userSettings with recovered profile
|
||||
userSettingsStore.setWalletId(walletProfileStore.walletId)
|
||||
await delay(1000)
|
||||
setStatusMessage(`Recovery completed`)
|
||||
setStatusMessage(translate('recovery.completed'))
|
||||
await delay(2000)
|
||||
} else {
|
||||
setInfo(`You used wallet address ${profileToRecover.nip05} with your own keys, import it again.`)
|
||||
await delay(5000)
|
||||
setInfo(translate("recovery.ownKeysImportAgain", { addr: profileToRecover.nip05 }))
|
||||
await delay(5000)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,8 +627,8 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
style={$card}
|
||||
ContentComponent={
|
||||
<ListItem
|
||||
text='Mnemonic exists'
|
||||
subText='Your wallet already has another mnemonic in its secure storage. Recovery process works only with freshly installed wallet to avoid loss of your funds.'
|
||||
tx="recovery.mnemonicCollision"
|
||||
subTx="recovery.mnemonicCollisionDesc"
|
||||
leftIcon='faTriangleExclamation'
|
||||
// leftIconColor='red'
|
||||
style={$item}
|
||||
@@ -635,7 +639,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
onPress={onBack}
|
||||
text='Back'
|
||||
tx='common.back'
|
||||
preset='secondary'
|
||||
/>
|
||||
</View>
|
||||
@@ -649,7 +653,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
style={$card}
|
||||
ContentComponent={
|
||||
<ListItem
|
||||
text='Your mnemonic phrase'
|
||||
tx='backupScreen.mnemonicTitle'
|
||||
subText={mnemonic}
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='1'/></View>}
|
||||
style={$item}
|
||||
@@ -665,7 +669,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
ContentComponent={
|
||||
<ListItem
|
||||
text={profileToRecover.nip05}
|
||||
subText={'This is the wallet address linked to your seed. If you press Complete, your wallet profile will reset to above seed and address.'}
|
||||
subTx="profileToRecoverDesc"
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='2'/></View>}
|
||||
style={$item}
|
||||
/>
|
||||
@@ -674,7 +678,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
onPress={onCompleteAddress}
|
||||
text={'Complete recovery'}
|
||||
tx="recovery.completeCTA"
|
||||
style={{marginRight: spacing.small}}
|
||||
/>
|
||||
</View>
|
||||
@@ -683,7 +687,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
onPress={onFindWalletAddress}
|
||||
text={'Find wallet address'}
|
||||
tx="recovery.findAddressCTA"
|
||||
style={{marginRight: spacing.small}}
|
||||
/>
|
||||
</View>
|
||||
@@ -696,8 +700,8 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
HeadingComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text='Recovery from mints'
|
||||
subText='Identify mints to recover your ecash from and add them to the list.'
|
||||
tx="recoveryFromMints"
|
||||
subTx="recoveryFromMintsDesc"
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='2'/></View>}
|
||||
RightComponent={mintsStore.mintCount > 0 ? (
|
||||
<View style={$rightContainer}>
|
||||
@@ -715,7 +719,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
onPress={onAddMints}
|
||||
text='Add mints'
|
||||
tx="addMints"
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
@@ -744,13 +748,13 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
{(startIndex > 0 || totalRecoveredAmount > 0) && (
|
||||
<Button
|
||||
onPress={onComplete}
|
||||
text={'Complete'}
|
||||
tx="common.complete"
|
||||
style={{marginRight: spacing.small}}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
onPress={startRecovery}
|
||||
text={startIndex === 0 ? 'Start recovery' : 'Next interval'}
|
||||
tx={startIndex === 0 ? 'startRecovery' : 'nextInterval'}
|
||||
preset={(startIndex === 0 || totalRecoveredAmount > 0) ? 'default' : 'secondary'}
|
||||
disabled={selectedMintUrl ? false : true}
|
||||
/>
|
||||
@@ -758,13 +762,16 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
|
||||
<View style={$buttonContainer}>
|
||||
<Text
|
||||
text={`Recovery interval ${startIndex} - ${endIndex} · `}
|
||||
text={translate("recovery.intervalParam", {
|
||||
startIndex: startIndex,
|
||||
endIndex: endIndex
|
||||
})}
|
||||
size='xxs'
|
||||
style={{color: textHint, alignSelf: 'center', marginTop: spacing.small}}
|
||||
/>
|
||||
<Pressable onPress={toggleIndexModal}>
|
||||
<Text
|
||||
text={`Set manually`}
|
||||
tx="recovery.setManually"
|
||||
size='xxs'
|
||||
style={{color: textHint, alignSelf: 'center', marginTop: spacing.small}}
|
||||
/>
|
||||
@@ -772,14 +779,17 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
</View>
|
||||
<View style={[$buttonContainer,{marginTop: 0}]}>
|
||||
<Text
|
||||
text={`Keyset ID ${selectedKeyset?.id} (${selectedKeyset?.unit})`}
|
||||
text={translate("recovery.keysetID", {
|
||||
id: selectedKeyset?.id,
|
||||
unit: selectedKeyset?.unit
|
||||
})}
|
||||
size='xxs'
|
||||
style={{color: textHint, alignSelf: 'center', marginTop: spacing.extraSmall}}
|
||||
/>
|
||||
{selectedMintKeysets.length > 1 && (
|
||||
<Pressable onPress={toggleKeysetModal}>
|
||||
<Text
|
||||
text={` · Select another`}
|
||||
tx="recovery.selectAnotherKeyset"
|
||||
size='xxs'
|
||||
style={{color: textHint, alignSelf: 'center', marginTop: spacing.extraSmall}}
|
||||
/>
|
||||
@@ -790,7 +800,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
)}
|
||||
{mintsStore.mintCount > 0 && !selectedMintUrl && (
|
||||
<Text
|
||||
text={`Select mint to recover from`}
|
||||
tx='recovery.selectMintFrom'
|
||||
size='xxs'
|
||||
style={{color: textHint, alignSelf: 'center', margin: spacing.large}}
|
||||
/>
|
||||
@@ -806,8 +816,11 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
style={$card}
|
||||
ContentComponent={
|
||||
<ListItem
|
||||
text='Insert mnemonic phrase'
|
||||
subText={`Paste or rewrite 12 word phrase to recover your ${isAddressOnlyRecovery ? 'wallet address' : 'ecash balance and wallet address'} on this device. Separate words by blank spaces.`}
|
||||
tx="recoveryInsertMnemonic"
|
||||
subTx={isAddressOnlyRecovery
|
||||
? 'recoveryInsertMnemonicDescAddrOnly'
|
||||
: 'recoveryInsertMnemonicDesc'
|
||||
}
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='1'/></View>}
|
||||
style={$item}
|
||||
/>
|
||||
@@ -823,7 +836,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
autoCapitalize='none'
|
||||
keyboardType='default'
|
||||
maxLength={150}
|
||||
placeholder='Mnemonic phrase...'
|
||||
placeholder={translate("mnemonicPhrasePlaceholder")}
|
||||
selectTextOnFocus={true}
|
||||
style={[$mnemonicInput, {backgroundColor: inputBg, flexWrap: 'wrap'}]}
|
||||
/>
|
||||
@@ -831,12 +844,12 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
{mnemonic ? (
|
||||
<Button
|
||||
onPress={onConfirm}
|
||||
text='Confirm'
|
||||
tx='common.confirm'
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
onPress={onPaste}
|
||||
text='Paste'
|
||||
tx='common.paste'
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -852,7 +865,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
isVisible={isIndexModalVisible}
|
||||
ContentComponent={
|
||||
<View style={$indexContainer}>
|
||||
<Text text="Set start index" preset="subheading" />
|
||||
<Text tx="setStartIndex" preset="subheading" />
|
||||
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
||||
<TextInput
|
||||
ref={indexInputRef}
|
||||
@@ -865,12 +878,12 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
textAlign='right'
|
||||
/>
|
||||
<Button
|
||||
text="Save"
|
||||
tx='common.save'
|
||||
onPress={onResetStartIndex}
|
||||
/>
|
||||
</View>
|
||||
<Text
|
||||
text={`Use to increase starting index of recovery interval e.g. in case of repeated recovery. You need to know at what index you completed your previous recovery or you'll miss ecash to recover.`}
|
||||
tx="recovery.startIndexDesc"
|
||||
size='xxs'
|
||||
style={{color: textHint, margin: spacing.small}}
|
||||
/>
|
||||
@@ -882,7 +895,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
<BottomModal
|
||||
isVisible={isKeysetModalVisible}
|
||||
// style={{alignItems: 'stretch'}}
|
||||
HeadingComponent={<Text text="Select keyset to be used for recovery" style={{textAlign: 'center', margin: spacing.small}}/>}
|
||||
HeadingComponent={<Text tx="recovery.selectKeyset" style={{textAlign: 'center', margin: spacing.small}}/>}
|
||||
ContentComponent={
|
||||
<FlatList
|
||||
data={selectedMintKeysets}
|
||||
@@ -911,7 +924,7 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
<Button
|
||||
preset={'secondary'}
|
||||
onPress={toggleKeysetModal}
|
||||
text={`Close`}
|
||||
tx='common.close'
|
||||
style={{marginTop: spacing.small}}
|
||||
/>}
|
||||
onBackButtonPress={toggleKeysetModal}
|
||||
@@ -949,13 +962,13 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
<ResultModalInfo
|
||||
icon="faCheckCircle"
|
||||
iconColor={colors.palette.success200}
|
||||
title="Recovery success!"
|
||||
title={translate("recovery.success")}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={toggleResultModal}
|
||||
/>
|
||||
</View>
|
||||
@@ -967,13 +980,13 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.angry500}
|
||||
title="Recovery failed"
|
||||
title={translate("recovery.failed")}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
text={'Show errors'}
|
||||
tx="showErrors"
|
||||
onPress={toggleErrorsModal}
|
||||
/>
|
||||
</View>
|
||||
@@ -985,13 +998,13 @@ export const RemoteRecoveryScreen: FC<AppStackScreenProps<'RemoteRecovery'>> = o
|
||||
<ResultModalInfo
|
||||
icon='faInfoCircle'
|
||||
iconColor={colors.palette.neutral400}
|
||||
title="No ecash recovered"
|
||||
title={translate("noEcashRecovered")}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={toggleResultModal}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -20,6 +20,7 @@ import { LnurlUtils } from '../services/lnurl/lnurlUtils'
|
||||
import { infoMessage } from '../utils/utils'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { useStores } from '../models'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
const hasAndroidCameraPermission = async () => {
|
||||
const cameraPermission = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA)
|
||||
@@ -113,7 +114,7 @@ export const ScanScreen: FC<WalletStackScreenProps<'Scan'>> = function ScanScree
|
||||
}
|
||||
|
||||
e.params = incoming
|
||||
e.message = 'Could not extract ecash token nor LNURL withdraw link to receive.'
|
||||
e.message = translate("scanReceiveExtractFail")
|
||||
handleError(e)
|
||||
break
|
||||
}
|
||||
@@ -170,12 +171,12 @@ export const ScanScreen: FC<WalletStackScreenProps<'Scan'>> = function ScanScree
|
||||
default:
|
||||
try {
|
||||
// generic scan button on wallet screen
|
||||
const incomingData = IncomingParser.findAndExtract(incoming)
|
||||
return IncomingParser.navigateWithIncomingData(incomingData, navigation, unit)
|
||||
const incomingData = IncomingParser.findAndExtract(incoming)
|
||||
return IncomingParser.navigateWithIncomingData(incomingData, navigation, unit)
|
||||
} catch (e: any) {
|
||||
e.name = Err.VALIDATION_ERROR
|
||||
e.params = {caller: 'onIncomingData', clipboard: incoming.slice(0, 100)}
|
||||
handleError(e)
|
||||
e.name = Err.VALIDATION_ERROR
|
||||
e.params = {caller: 'onIncomingData', clipboard: incoming.slice(0, 100)}
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +185,7 @@ export const ScanScreen: FC<WalletStackScreenProps<'Scan'>> = function ScanScree
|
||||
const onPaste = async function() {
|
||||
const clipboard = await Clipboard.getString()
|
||||
if (clipboard.length === 0) {
|
||||
infoMessage('First copy ecash token, invoice, LNURL link or lightning address. Then paste.')
|
||||
infoMessage(translate("scanScreen.onPasteEmptyClipboard"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -215,7 +216,7 @@ export const ScanScreen: FC<WalletStackScreenProps<'Scan'>> = function ScanScree
|
||||
LeftAccessory={() => (
|
||||
<Icon icon='faPaste'/>
|
||||
)}
|
||||
text={'Paste'}
|
||||
tx='common.paste'
|
||||
preset='secondary'
|
||||
style={{marginTop: spacing.medium, minWidth: 120}}
|
||||
/>
|
||||
|
||||
@@ -333,14 +333,14 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
log.trace('[onAmountEndEditing]', amount)
|
||||
|
||||
if (!amount || amount === 0) {
|
||||
infoMessage('Amount should be positive number.')
|
||||
infoMessage(translate('payCommon.amountZeroOrNegative'))
|
||||
return
|
||||
}
|
||||
|
||||
const availableBalances = proofsStore.getMintBalancesWithEnoughBalance(amount, unit)
|
||||
|
||||
if (availableBalances.length === 0) {
|
||||
infoMessage('There are not enough funds to send this amount.')
|
||||
infoMessage(translate('payCommon.insufficientFunds'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -684,7 +684,7 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
/>
|
||||
{transaction?.memo && (
|
||||
<TranItem
|
||||
label="tranDetailScreen.memoToReceiver"
|
||||
label="receiverMemo"
|
||||
value={transaction.memo as string}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -178,7 +178,7 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
tx='settingsScreen.manageMints'
|
||||
tx='manageMints'
|
||||
leftIcon='faCoins'
|
||||
leftIconColor={colors.palette.iconBlue300}
|
||||
leftIconInverse={true}
|
||||
@@ -219,7 +219,7 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text='Push notifications'
|
||||
tx="pushNotifications"
|
||||
subText={`Token: ${walletProfileStore.device?.slice(0, 10)}...`}
|
||||
leftIcon='faPaperPlane'
|
||||
leftIconColor={colors.palette.green400}
|
||||
@@ -229,7 +229,7 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
|
||||
<View style={$rightContainer}>
|
||||
<Text
|
||||
style={$itemRight}
|
||||
text={areNotificationsEnabled ? 'Enabled' : 'Disabled'}
|
||||
tx={areNotificationsEnabled ? 'common.enabled' : 'common.disabled'}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
@@ -237,8 +237,8 @@ export const SettingsScreen: FC<SettingsScreenProps> = observer(
|
||||
onPress={openNotificationSettings}
|
||||
/>
|
||||
<ListItem
|
||||
text={'Nostr relays'}
|
||||
subText={`Connected: ${relaysStore.connectedCount}`}
|
||||
tx="nostr.relaysTitle"
|
||||
subText={translate("common.connectedParam", { param: relaysStore.connectedCount })}
|
||||
leftIcon='faCircleNodes'
|
||||
leftIconColor={colors.palette.iconViolet200}
|
||||
leftIconInverse={true}
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useStores } from '../models'
|
||||
import { Mint } from '../models/Mint'
|
||||
import { MintHeader } from './Mints/MintHeader'
|
||||
import { moderateVerticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
|
||||
export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = function TokenReceiveScreen(_props) {
|
||||
@@ -46,7 +47,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
|
||||
log.trace('[TokenReceiveScreen.setUnitAndMint]')
|
||||
const {unit, mintUrl} = route.params
|
||||
if(!unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint unit in route params')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("missingMintUnitRouteParamsError"))
|
||||
}
|
||||
|
||||
setUnit(unit)
|
||||
@@ -74,7 +75,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
|
||||
const onPaste = async function() {
|
||||
const clipboard = await Clipboard.getString()
|
||||
if (clipboard.length === 0) {
|
||||
infoMessage('First copy ecash token, then paste.')
|
||||
infoMessage(translate('tokenReceiveScreen.onPasteEmptyClipboard'))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -98,7 +99,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
|
||||
|
||||
const onConfirm = async function() {
|
||||
if(!encodedToken) {
|
||||
setError({name: Err.VALIDATION_ERROR, message: 'Missing ecash token to receive.'})
|
||||
setError({name: Err.VALIDATION_ERROR, message: translate("missingEcashTokenToReceiveError")})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -143,7 +144,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text
|
||||
preset="heading"
|
||||
text={'Receive ecash'}
|
||||
tx="payCommon.receiveEcash"
|
||||
style={{color: 'white'}}
|
||||
/>
|
||||
</View>
|
||||
@@ -153,7 +154,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
|
||||
<ListItem
|
||||
leftIcon='faMoneyBill1'
|
||||
leftIconColor={colors.palette.iconViolet300}
|
||||
text='Paste ecash token'
|
||||
tx="common.pasteEcashToken"
|
||||
bottomSeparator={true}
|
||||
/* RightComponent={
|
||||
<Button
|
||||
@@ -171,7 +172,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
|
||||
<Text
|
||||
size='xs'
|
||||
style={{color: hintText, padding: spacing.extraSmall}}
|
||||
text='Paste ecash token you want to receive.'
|
||||
tx="pasteEcashTokenDesc"
|
||||
/>
|
||||
<View style={{alignItems: 'center', marginTop: spacing.small}}>
|
||||
<TextInput
|
||||
@@ -191,7 +192,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset='default'
|
||||
tx={'common.confirm'}
|
||||
tx='common.confirm'
|
||||
onPress={onConfirm}
|
||||
style={{marginLeft: spacing.small}}
|
||||
LeftAccessory={() => <Icon icon='faCheckCircle' color='white'/>}
|
||||
@@ -209,7 +210,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
|
||||
/>
|
||||
<Button
|
||||
preset='secondary'
|
||||
tx={'common.scan'}
|
||||
tx='common.scan'
|
||||
onPress={gotoScan}
|
||||
style={{marginLeft: spacing.small}}
|
||||
LeftAccessory={() => {
|
||||
@@ -230,7 +231,7 @@ export const TokenReceiveScreen: FC<WalletStackScreenProps<'TokenReceive'>> = fu
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
text={'Topup with Lightning'}
|
||||
tx="tokenReceiveScreen.topupWithLightning"
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faBolt'
|
||||
|
||||
+981
-849
File diff suppressed because it is too large
Load Diff
@@ -175,25 +175,25 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
|
||||
try {
|
||||
Clipboard.setString(JSON.stringify(getAuditTrail(transaction)))
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not copy: ${e.message}`)
|
||||
setInfo(translate("common.copyFailParam", { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
const copyToken = function (transaction: Transaction) {
|
||||
try {
|
||||
const encoded = getEncodedTokenToSend(transaction)
|
||||
const encoded = getEncodedTokenToSend(transaction)
|
||||
|
||||
if (!encoded) {
|
||||
if (!encoded) {
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
'Could not get encoded ecash token from transaction',
|
||||
Err.VALIDATION_ERROR,
|
||||
'Could not get encoded ecash token from transaction',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Clipboard.setString(encoded)
|
||||
Clipboard.setString(encoded)
|
||||
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not copy: ${e.message}`)
|
||||
setInfo(translate("common.copyFailParam", { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -202,7 +202,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
|
||||
try {
|
||||
Clipboard.setString(JSON.stringify(proofsByStatus))
|
||||
} catch (e: any) {
|
||||
setInfo(`Could not copy: ${e.message}`)
|
||||
setInfo(translate("common.copyFailParam", { param: e.message }))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,9 +222,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
|
||||
|
||||
|
||||
const getFormattedAmount = function(): string {
|
||||
if (!transaction) {
|
||||
return ''
|
||||
}
|
||||
if (!transaction) { return '' }
|
||||
|
||||
switch (transaction?.type) {
|
||||
case TransactionType.RECEIVE || TransactionType.RECEIVE_OFFLINE:
|
||||
@@ -274,7 +272,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
|
||||
? transaction.noteToSelf
|
||||
: savedNote
|
||||
? savedNote
|
||||
: 'Add your note'
|
||||
: translate("tranDetailScreen.addYourNote")
|
||||
}
|
||||
LeftComponent={
|
||||
<Icon
|
||||
@@ -289,7 +287,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
|
||||
onPress={toggleNoteModal}
|
||||
/>
|
||||
<ListItem
|
||||
text="Add tags"
|
||||
tx="tranDetailScreen.addTags"
|
||||
LeftComponent={
|
||||
<Icon
|
||||
containerStyle={$iconContainer}
|
||||
@@ -357,7 +355,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
|
||||
<>
|
||||
<Text
|
||||
style={{color: labelColor, fontSize: 14}}
|
||||
text="Backed up ecash"
|
||||
tx="tranDetailScreen.backedUpEcash"
|
||||
/>
|
||||
<JSONTree
|
||||
hideRoot
|
||||
@@ -374,7 +372,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={() => copyBackupProofs(proofsByStatus)}
|
||||
text="Copy"
|
||||
tx="common.copy"
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
@@ -393,7 +391,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
|
||||
isVisible={isNoteModalVisible}
|
||||
ContentComponent={
|
||||
<View style={$noteContainer}>
|
||||
<Text text="Add your note" preset="subheading" />
|
||||
<Text tx="tranDetailScreen.addYourNote" preset="subheading" />
|
||||
<View style={{flexDirection: 'row', alignItems: 'center'}}>
|
||||
<TextInput
|
||||
ref={noteInputRef}
|
||||
@@ -403,7 +401,7 @@ export const TranDetailScreen: FC<TransactionsStackScreenProps<'TranDetail'>> =
|
||||
maxLength={200}
|
||||
/>
|
||||
<Button
|
||||
text="Save"
|
||||
tx="common.save"
|
||||
onPress={saveNote}
|
||||
/>
|
||||
</View>
|
||||
@@ -459,7 +457,7 @@ const ReceiveInfoBlock = function (props: {
|
||||
|
||||
const transactionDataUpdate = {
|
||||
status: TransactionStatus.EXPIRED,
|
||||
message: 'Ecash has been successfully received after retry. New transaction has been created.',
|
||||
message: translate("tranDetailScreen.successRetrieveEcashAfterRetry"),
|
||||
createdAt: new Date(),
|
||||
}
|
||||
|
||||
@@ -567,7 +565,7 @@ const ReceiveInfoBlock = function (props: {
|
||||
<Button
|
||||
style={{marginTop: spacing.medium}}
|
||||
// preset="secondary"
|
||||
text="Retry to receive"
|
||||
tx="tranDetailScreen.retryToReceive"
|
||||
onPress={onRetryToReceive}
|
||||
/>
|
||||
</View>
|
||||
@@ -597,7 +595,7 @@ const ReceiveInfoBlock = function (props: {
|
||||
style={$dataCard}
|
||||
ContentComponent={
|
||||
<TranItem
|
||||
label="tranDetailScreen.receivedTo"
|
||||
label="transactionCommon.receivedTo"
|
||||
value={transaction.mint as string}
|
||||
/>
|
||||
}
|
||||
@@ -610,7 +608,7 @@ const ReceiveInfoBlock = function (props: {
|
||||
<>
|
||||
<Text
|
||||
style={{color: labelColor, fontSize: 14}}
|
||||
text="Audit trail"
|
||||
tx="tranDetailScreen.auditTrail"
|
||||
/>
|
||||
<JSONTree
|
||||
hideRoot
|
||||
@@ -649,7 +647,7 @@ const ReceiveInfoBlock = function (props: {
|
||||
<ResultModalInfo
|
||||
icon="faCheckCircle"
|
||||
iconColor={colors.palette.success200}
|
||||
title="Success!"
|
||||
title={translate("tranDetailScreen.modalSuccess")}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
@@ -667,13 +665,13 @@ const ReceiveInfoBlock = function (props: {
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.angry500}
|
||||
title="Receive failed"
|
||||
title={translate('transactionCommon.receiveFailed')}
|
||||
message={resultModalInfo?.message as string}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={toggleResultModal}
|
||||
/>
|
||||
</View>
|
||||
@@ -793,7 +791,7 @@ const ReceiveOfflineInfoBlock = function (props: {
|
||||
) : (
|
||||
<TranItem
|
||||
label="tranDetailScreen.amount"
|
||||
value={`${transaction.amount}`}
|
||||
value={transaction.amount}
|
||||
isFirst={true}
|
||||
/>
|
||||
)}
|
||||
@@ -833,7 +831,7 @@ const ReceiveOfflineInfoBlock = function (props: {
|
||||
style={$dataCard}
|
||||
ContentComponent={
|
||||
<TranItem
|
||||
label="tranDetailScreen.receivedTo"
|
||||
label="transactionCommon.receivedTo"
|
||||
value={transaction.mint as string}
|
||||
/>
|
||||
}
|
||||
@@ -846,7 +844,7 @@ const ReceiveOfflineInfoBlock = function (props: {
|
||||
<>
|
||||
<Text
|
||||
style={{color: labelColor, fontSize: 14}}
|
||||
text="Audit trail"
|
||||
tx='tranDetailScreen.auditTrail'
|
||||
/>
|
||||
<JSONTree
|
||||
hideRoot
|
||||
@@ -886,13 +884,13 @@ const ReceiveOfflineInfoBlock = function (props: {
|
||||
<ResultModalInfo
|
||||
icon="faCheckCircle"
|
||||
iconColor={colors.palette.success200}
|
||||
title="Success!"
|
||||
title={translate('common.success')}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={onGoBack}
|
||||
/>
|
||||
</View>
|
||||
@@ -904,13 +902,13 @@ const ReceiveOfflineInfoBlock = function (props: {
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.angry500}
|
||||
title="Receive failed"
|
||||
title={translate('transactionCommon.receiveFailed')}
|
||||
message={resultModalInfo?.message as string}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={onGoBack}
|
||||
/>
|
||||
</View>
|
||||
@@ -950,7 +948,7 @@ const SendInfoBlock = function (props: {
|
||||
isFirst={true}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.memoToReceiver"
|
||||
label="receiverMemo"
|
||||
value={transaction.memo as string}
|
||||
/>
|
||||
{transaction.sentTo && (
|
||||
@@ -1025,7 +1023,7 @@ const SendInfoBlock = function (props: {
|
||||
fontSize: 14,
|
||||
marginTop: spacing.small,
|
||||
}}
|
||||
text="Pending token"
|
||||
tx="tranDetailScreen.pendingToken"
|
||||
/>
|
||||
<Text
|
||||
text={getEncodedTokenToSend(transaction) as string}
|
||||
@@ -1041,7 +1039,7 @@ const SendInfoBlock = function (props: {
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={() => copyToken(transaction)}
|
||||
text="Copy"
|
||||
tx='common.copy'
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
@@ -1060,7 +1058,7 @@ const SendInfoBlock = function (props: {
|
||||
<>
|
||||
<Text
|
||||
style={{color: labelColor, fontSize: 14}}
|
||||
text="Audit trail"
|
||||
tx='tranDetailScreen.auditTrail'
|
||||
/>
|
||||
<JSONTree
|
||||
hideRoot
|
||||
@@ -1078,7 +1076,7 @@ const SendInfoBlock = function (props: {
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={() => copyAuditTrail(transaction)}
|
||||
text="Copy"
|
||||
tx='common.copy'
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
@@ -1195,7 +1193,7 @@ const TopupInfoBlock = function (props: {
|
||||
isFirst={true}
|
||||
/>
|
||||
<TranItem
|
||||
label="tranDetailScreen.memoToReceiver"
|
||||
label="receiverMemo"
|
||||
value={transaction.memo as string}
|
||||
/>
|
||||
{transaction.sentFrom && (
|
||||
@@ -1218,7 +1216,7 @@ const TopupInfoBlock = function (props: {
|
||||
<Button
|
||||
style={{marginTop: spacing.medium}}
|
||||
preset="secondary"
|
||||
text="Retry to complete"
|
||||
tx="tranDetailScreen.retryToComplete"
|
||||
onPress={onRetryToHandlePendingTopup}
|
||||
/>
|
||||
</View>
|
||||
@@ -1254,10 +1252,10 @@ const TopupInfoBlock = function (props: {
|
||||
/>
|
||||
{transaction.status === TransactionStatus.PENDING && paymentRequest && (
|
||||
<>
|
||||
<Text style={{color: labelColor, marginTop: spacing.medium}} text='Invoice to pay'/>
|
||||
<View style={$qrCodeContainer}>
|
||||
<QRCode size={270} value={paymentRequest.encodedInvoice} />
|
||||
</View>
|
||||
<Text style={{ color: labelColor, marginTop: spacing.medium }} tx="tranDetailScreen.invoiceToPay" />
|
||||
<View style={$qrCodeContainer}>
|
||||
<QRCode size={270} value={paymentRequest.encodedInvoice} />
|
||||
</View>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
@@ -1266,16 +1264,16 @@ const TopupInfoBlock = function (props: {
|
||||
<>
|
||||
{transaction.status === TransactionStatus.PENDING && paymentRequest && (
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={() => copyInvoice()}
|
||||
text="Copy"
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
marginTop: spacing.small,
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
textStyle={{fontSize: 14}}
|
||||
preset="tertiary"
|
||||
onPress={() => copyInvoice()}
|
||||
tx='common.copy'
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
marginTop: spacing.small,
|
||||
alignSelf: 'center',
|
||||
}}
|
||||
textStyle={{fontSize: 14}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
@@ -1289,7 +1287,7 @@ const TopupInfoBlock = function (props: {
|
||||
<>
|
||||
<Text
|
||||
style={{color: labelColor, fontSize: 14}}
|
||||
text="Audit trail"
|
||||
tx="tranDetailScreen.auditTrail"
|
||||
/>
|
||||
<JSONTree
|
||||
hideRoot
|
||||
@@ -1306,7 +1304,7 @@ const TopupInfoBlock = function (props: {
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={() => copyAuditTrail(transaction)}
|
||||
text="Copy"
|
||||
tx="common.copy"
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
@@ -1328,13 +1326,13 @@ const TopupInfoBlock = function (props: {
|
||||
<ResultModalInfo
|
||||
icon="faCheckCircle"
|
||||
iconColor={colors.palette.success200}
|
||||
title="Success!"
|
||||
title={translate("tranDetailScreen.modalSuccess")}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={onGoBack}
|
||||
/>
|
||||
</View>
|
||||
@@ -1345,7 +1343,7 @@ const TopupInfoBlock = function (props: {
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.accent400}
|
||||
title="Invoice not paid"
|
||||
title={translate("tranDetailScreen.invoiceNotPaid")}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
@@ -1362,13 +1360,13 @@ const TopupInfoBlock = function (props: {
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.accent400}
|
||||
title="Invoice expired"
|
||||
title={translate("tranDetailScreen.invoiceExpired")}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={toggleResultModal}
|
||||
/>
|
||||
</View>
|
||||
@@ -1379,13 +1377,13 @@ const TopupInfoBlock = function (props: {
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.angry500}
|
||||
title="Topup error"
|
||||
title={translate("tranDetailScreen.topupError")}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
tx='common.close'
|
||||
onPress={toggleResultModal}
|
||||
/>
|
||||
</View>
|
||||
@@ -1471,8 +1469,8 @@ const TransferInfoBlock = function (props: {
|
||||
style={$dataCard}
|
||||
ContentComponent={
|
||||
<TranItem
|
||||
label="tranDetailScreen.paidFrom"
|
||||
value={transaction.mint as string}
|
||||
label="transactionCommon.paidFrom"
|
||||
value={transaction.mint as string}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -1484,7 +1482,7 @@ const TransferInfoBlock = function (props: {
|
||||
<>
|
||||
<Text
|
||||
style={{color: labelColor, fontSize: 14}}
|
||||
text="Audit trail"
|
||||
tx="tranDetailScreen.auditTrail"
|
||||
/>
|
||||
<JSONTree
|
||||
hideRoot
|
||||
@@ -1502,7 +1500,7 @@ const TransferInfoBlock = function (props: {
|
||||
<Button
|
||||
preset="tertiary"
|
||||
onPress={() => copyAuditTrail(transaction)}
|
||||
text="Copy"
|
||||
tx="common.copy"
|
||||
style={{
|
||||
minHeight: 25,
|
||||
paddingVertical: spacing.extraSmall,
|
||||
|
||||
@@ -33,10 +33,11 @@ import AppError from '../utils/AppError'
|
||||
import {TransactionListItem} from './Transactions/TransactionListItem'
|
||||
import { Transaction, TransactionStatus } from '../models/Transaction'
|
||||
import { height } from '@fortawesome/free-solid-svg-icons/faWallet'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
if (Platform.OS === 'android' &&
|
||||
UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true)
|
||||
UIManager.setLayoutAnimationEnabledExperimental) {
|
||||
UIManager.setLayoutAnimationEnabledExperimental(true)
|
||||
}
|
||||
// Number of transactions held in TransactionsStore model
|
||||
const limit = maxTransactionsInModel
|
||||
@@ -248,7 +249,16 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text={showPendingOnly ? `Showing ${transactionsStore.pending.length} of ${pendingDbCount} pending` : `Showing ${transactionsStore.count} of ${dbCount} total`}
|
||||
text={showPendingOnly
|
||||
? translate("tranHistory.showingPaginationPending", {
|
||||
amount: transactionsStore.pending.length,
|
||||
total: pendingDbCount
|
||||
})
|
||||
: translate("tranHistory.showingPaginationTotal", {
|
||||
amount: transactionsStore.count,
|
||||
total: dbCount
|
||||
})
|
||||
}
|
||||
LeftComponent={
|
||||
<Icon
|
||||
containerStyle={$iconContainer}
|
||||
@@ -262,7 +272,9 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
|
||||
onPress={() => false}
|
||||
/>
|
||||
<ListItem
|
||||
text={`${transactionsStore.pending.length} Pending transactions`}
|
||||
text={translate("tranHistory.pendingParam", {
|
||||
param: transactionsStore.pending.length
|
||||
})}
|
||||
LeftComponent={
|
||||
<Icon
|
||||
containerStyle={$iconContainer}
|
||||
@@ -308,12 +320,12 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
|
||||
{sections.length > 0 && (
|
||||
<View style={{alignItems: 'center', marginTop: spacing.small}}>
|
||||
{!showPendingOnly && isAll || showPendingOnly && pendingIsAll ? (
|
||||
<Text text="List is complete" size="xs" />
|
||||
<Text tx="tranHistory.listIsComplete" size="xs" />
|
||||
) : (
|
||||
<Button
|
||||
preset="secondary"
|
||||
onPress={getTransactionsList}
|
||||
text="View more"
|
||||
tx="tranHistory.viewMore"
|
||||
style={{minHeight: 25, paddingVertical: spacing.tiny}}
|
||||
textStyle={{fontSize: 14}}
|
||||
/>
|
||||
@@ -329,8 +341,8 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
|
||||
<Card
|
||||
ContentComponent={
|
||||
<ListItem
|
||||
leftIcon='faBan'
|
||||
text={"No transactions to show."}
|
||||
leftIcon='faBan'
|
||||
tx={"tranHistory.noTransToShow"}
|
||||
/>
|
||||
}
|
||||
style={$card}
|
||||
@@ -346,15 +358,19 @@ export const TranHistoryScreen: FC<TransactionsStackScreenProps<'TranHistory'>>
|
||||
ContentComponent={
|
||||
<>
|
||||
<ListItem
|
||||
text='Delete expired'
|
||||
subText={`This will delete ${expiredDbCount} expired transactions`}
|
||||
tx="tranHistory.deleteExpired"
|
||||
subText={translate("tranHistory.deleteExpiredDesc", {
|
||||
count: expiredDbCount
|
||||
})}
|
||||
leftIcon='faRotate'
|
||||
onPress={onDeleteExpired}
|
||||
bottomSeparator={true}
|
||||
/>
|
||||
<ListItem
|
||||
text="Delete with errors"
|
||||
subText={`This will delete ${erroredDbCount} transactions with errors`}
|
||||
tx="tranHistory.deleteErrored"
|
||||
subText={translate("tranHistory.deleteErroredDesc", {
|
||||
count: erroredDbCount
|
||||
})}
|
||||
leftIcon='faBug'
|
||||
onPress={onDeleteErrored}
|
||||
/>
|
||||
|
||||
@@ -50,6 +50,7 @@ import { MintBalanceSelector } from './Mints/MintBalanceSelector'
|
||||
import numbro from 'numbro'
|
||||
import { TranItem } from './TranDetailScreen'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
|
||||
if (
|
||||
@@ -116,7 +117,7 @@ useEffect(() => {
|
||||
try {
|
||||
const {unit, mintUrl} = route.params
|
||||
if(!unit) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint unit in route params')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate('missingMintUnitRouteParamsError'))
|
||||
}
|
||||
|
||||
setUnit(unit)
|
||||
@@ -180,7 +181,7 @@ useFocusEffect(
|
||||
const {lnurlParams} = route.params
|
||||
|
||||
if (!lnurlParams) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing LNURL params.')
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate('missingLNURLParamsError'))
|
||||
}
|
||||
|
||||
const metadata = lnurlParams.decodedMetadata
|
||||
@@ -291,7 +292,10 @@ useEffect(() => {
|
||||
let availableBalances = proofsStore.getMintBalancesWithEnoughBalance(totalAmount, unit)
|
||||
|
||||
if (availableBalances.length === 0) {
|
||||
infoMessage(`There is not enough balance in ${getCurrency(unit).code} to pay the invoice amount and expected fees: ${amountToTransfer} ${getCurrency(unit).code}`)
|
||||
infoMessage(translate("transferScreen.insufficientFunds", {
|
||||
currency: getCurrency(unit).code,
|
||||
amount: amountToTransfer
|
||||
}))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -347,7 +351,7 @@ useEffect(() => {
|
||||
} else {
|
||||
setResultModalInfo({
|
||||
status,
|
||||
title: error.params?.message ? error.message : 'Payment failed',
|
||||
title: error.params?.message ? error.message : translate('payCommon.failed'),
|
||||
message: error.params?.message || error.message,
|
||||
})
|
||||
}
|
||||
@@ -418,50 +422,51 @@ const onMintBalanceSelect = function (balance: MintBalance) {
|
||||
|
||||
// Amount is editable only in case of LNURL Pay, while invoice is not yet retrieved
|
||||
const onAmountEndEditing = async function () {
|
||||
try {
|
||||
const precision = getCurrency(unit).precision
|
||||
const mantissa = getCurrency(unit).mantissa
|
||||
const amount = round(toNumber(amountToTransfer) * precision, 0)
|
||||
try {
|
||||
const precision = getCurrency(unit).precision
|
||||
const mantissa = getCurrency(unit).mantissa
|
||||
const amount = round(toNumber(amountToTransfer) * precision, 0)
|
||||
|
||||
if (!amount || amount === 0) {
|
||||
infoMessage('Amount should be positive number.')
|
||||
return
|
||||
}
|
||||
|
||||
if(!lnurlPayParams) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing LNURL pay parameters', {caller: 'onAmountEndEditing'})
|
||||
}
|
||||
|
||||
if (lnurlPayParams.minSendable && amount < lnurlPayParams.minSendable / 1000 ) {
|
||||
infoMessage(`Minimal amount to pay is ${roundUp(lnurlPayParams.minSendable / 1000, 0)} ${CurrencyCode.SATS}.`)
|
||||
return
|
||||
}
|
||||
|
||||
if (lnurlPayParams.maxSendable && amount > lnurlPayParams.maxSendable / 1000 ) {
|
||||
infoMessage(`Maximal amount to pay is ${roundDown(lnurlPayParams.maxSendable / 1000, 0)} ${CurrencyCode.SATS}.`)
|
||||
return
|
||||
}
|
||||
|
||||
if (lnurlPayParams.payerData) {
|
||||
infoMessage(`Minibits does not yet support entering of payer identity data (LUD18).`)
|
||||
}
|
||||
|
||||
setAmountToTransfer(`${numbro(amountToTransfer).format({thousandSeparated: true, mantissa: getCurrency(unit).mantissa})}`)
|
||||
|
||||
setIsLoading(true)
|
||||
const encoded = await LnurlClient.getInvoice(lnurlPayParams, amount * 1000)
|
||||
setIsLoading(false)
|
||||
|
||||
if(encoded) {
|
||||
return onEncodedInvoice(encoded)
|
||||
}
|
||||
|
||||
throw new AppError(Err.NOTFOUND_ERROR, `Could not get lightning invoice from ${lnurlPayParams.domain}`)
|
||||
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
if (!amount || amount === 0) {
|
||||
infoMessage(translate('payCommon.amountZeroOrNegative'))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(!lnurlPayParams) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing LNURL pay parameters', {caller: 'onAmountEndEditing'})
|
||||
}
|
||||
|
||||
if (lnurlPayParams.minSendable && amount < lnurlPayParams.minSendable / 1000) {
|
||||
infoMessage(translate('payCommon.minimumWithdraw', {
|
||||
amount: roundUp(lnurlPayParams.minSendable / 1000, 0),
|
||||
currency: CurrencyCode.SATS
|
||||
}))
|
||||
return;
|
||||
}
|
||||
|
||||
if (lnurlPayParams.maxSendable && amount > lnurlPayParams.maxSendable / 1000 ) {
|
||||
infoMessage(translate("payCommon.maximumPay", {
|
||||
amount: roundDown(lnurlPayParams.maxSendable / 1000, 0),
|
||||
currency: CurrencyCode.SATS
|
||||
}))
|
||||
return;
|
||||
}
|
||||
|
||||
if (lnurlPayParams.payerData) {
|
||||
infoMessage(translate("transferScreen.LUD18unsupported"))
|
||||
}
|
||||
|
||||
setAmountToTransfer(`${numbro(amountToTransfer).format({thousandSeparated: true, mantissa: getCurrency(unit).mantissa})}`)
|
||||
|
||||
setIsLoading(true)
|
||||
const encoded = await LnurlClient.getInvoice(lnurlPayParams, amount * 1000)
|
||||
setIsLoading(false)
|
||||
|
||||
if (encoded) return onEncodedInvoice(encoded);
|
||||
|
||||
throw new AppError(Err.NOTFOUND_ERROR, `Could not get lightning invoice from ${lnurlPayParams.domain}`)
|
||||
} catch (e: any) { handleError(e) }
|
||||
}
|
||||
|
||||
|
||||
const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: string = '') {
|
||||
@@ -479,23 +484,20 @@ const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: st
|
||||
log.trace('[onEncodedInvoice] Invoice data', {amount, expiresAt, description})
|
||||
|
||||
if (!amount || amount === 0) {
|
||||
infoMessage('Invoice amount should be positive number')
|
||||
return
|
||||
infoMessage(translate('payCommon.amountZeroOrNegative'))
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(!isInternetReachable) {
|
||||
setInfo('Your device is currently offline.')
|
||||
}
|
||||
if(!isInternetReachable) setInfo(translate('common.offlinePretty'));
|
||||
|
||||
setEncodedInvoice(encoded)
|
||||
setInvoice(invoice)
|
||||
setInvoiceExpiry(expiresAt)
|
||||
|
||||
if (paymentRequestDesc) {
|
||||
setMemo(paymentRequestDesc)
|
||||
setMemo(paymentRequestDesc)
|
||||
} else if(description) {
|
||||
setMemo(description)
|
||||
setMemo(description)
|
||||
}
|
||||
|
||||
// We need to retrieve the quote first to know how much is needed to settle invoice in selected currency unit
|
||||
@@ -505,29 +507,29 @@ const onEncodedInvoice = async function (encoded: string, paymentRequestDesc: st
|
||||
proofsStore.getMintBalancesWithUnit(unit)[0]
|
||||
|
||||
if (!balanceToTransferFrom) {
|
||||
infoMessage(`There is no mint with ${unit} balance from which payment can be made.`)
|
||||
return
|
||||
infoMessage(translate("transferScreen.noMintWithBalance", { unit }))
|
||||
return
|
||||
}
|
||||
|
||||
setMintBalanceToTransferFrom(balanceToTransferFrom)
|
||||
// continues in hook that handles other mint selection by user
|
||||
|
||||
} catch (e: any) {
|
||||
resetState()
|
||||
handleError(e)
|
||||
navigation.popToTop()
|
||||
resetState()
|
||||
handleError(e)
|
||||
navigation.popToTop()
|
||||
}
|
||||
}
|
||||
|
||||
const transfer = async function () {
|
||||
try {
|
||||
if(!meltQuote) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing quote to initiate transfer transaction')
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing quote to initiate transfer transaction')
|
||||
}
|
||||
|
||||
if (!mintBalanceToTransferFrom) {
|
||||
setInfo('Select mint balance to transfer from')
|
||||
return
|
||||
setInfo(translate("transferScreen.selectMintFrom"))
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true)
|
||||
@@ -601,7 +603,7 @@ const satsColor = colors.palette.primary200
|
||||
) : (
|
||||
<Text
|
||||
size='sm'
|
||||
text={'Amount to pay'}
|
||||
tx="payCommon.amountToPayLabel"
|
||||
style={{color: 'white', textAlign: 'center'}}
|
||||
/>
|
||||
)}
|
||||
@@ -613,7 +615,7 @@ const satsColor = colors.palette.primary200
|
||||
style={[$card, {minHeight: 50}]}
|
||||
ContentComponent={
|
||||
<ListItem
|
||||
text={lnurlPayParams?.address || memo || lnurlPayParams?.domain || 'No description'}
|
||||
text={lnurlPayParams?.address || memo || lnurlPayParams?.domain || translate("common.noDescPlaceholder")}
|
||||
subText={lnurlDescription}
|
||||
LeftComponent={
|
||||
<Icon
|
||||
@@ -634,8 +636,8 @@ const satsColor = colors.palette.primary200
|
||||
mintBalances={availableMintBalances}
|
||||
selectedMintBalance={mintBalanceToTransferFrom}
|
||||
unit={unit}
|
||||
title='Pay from'
|
||||
confirmTitle='Pay now'
|
||||
title={translate("payCommon.payFrom")}
|
||||
confirmTitle={translate("payCommon.payNow")}
|
||||
onMintBalanceSelect={onMintBalanceSelect}
|
||||
onCancel={onClose}
|
||||
onMintBalanceConfirm={transfer}
|
||||
@@ -696,7 +698,7 @@ const satsColor = colors.palette.primary200
|
||||
<ResultModalInfo
|
||||
icon="faCheckCircle"
|
||||
iconColor={colors.palette.success200}
|
||||
title="Payment completed"
|
||||
title={translate('payCommon.completed')}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
@@ -705,9 +707,9 @@ const satsColor = colors.palette.primary200
|
||||
tx={'common.close'}
|
||||
onPress={() => {
|
||||
if(isInvoiceDonation) {
|
||||
navigation.navigate('ContactsNavigator', {screen: 'Contacts', params: {}})
|
||||
navigation.navigate('ContactsNavigator', {screen: 'Contacts', params: {}})
|
||||
} else {
|
||||
navigation.navigate('Wallet', {})
|
||||
navigation.navigate('Wallet', {})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -720,7 +722,7 @@ const satsColor = colors.palette.primary200
|
||||
<ResultModalInfo
|
||||
icon="faRotate"
|
||||
iconColor={colors.palette.accent300}
|
||||
title="Transfer reverted"
|
||||
title={translate('transactionCommon.reverted')}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
@@ -738,7 +740,7 @@ const satsColor = colors.palette.primary200
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.angry500}
|
||||
title={resultModalInfo?.title || 'Payment failed'}
|
||||
title={resultModalInfo?.title || translate('payCommon.failed')}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
@@ -756,7 +758,7 @@ const satsColor = colors.palette.primary200
|
||||
<ResultModalInfo
|
||||
icon="faTriangleExclamation"
|
||||
iconColor={colors.palette.iconYellow300}
|
||||
title="Payment is pending"
|
||||
title={translate('payCommon.isPending')}
|
||||
message={resultModalInfo?.message}
|
||||
/>
|
||||
<View style={$buttonContainer}>
|
||||
|
||||
@@ -26,6 +26,7 @@ import AppError from '../utils/AppError'
|
||||
import { log } from '../services'
|
||||
import {Env} from '../utils/envtypes'
|
||||
import { CommonActions, StackActions } from '@react-navigation/native'
|
||||
import { translate } from '../i18n'
|
||||
|
||||
|
||||
const deploymentKey = APP_ENV === Env.PROD ? CODEPUSH_PRODUCTION_DEPLOYMENT_KEY : CODEPUSH_STAGING_DEPLOYMENT_KEY
|
||||
@@ -90,11 +91,11 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
|
||||
switch (status) {
|
||||
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
|
||||
log.trace('Downloading update...')
|
||||
setInfo('Downloading update...')
|
||||
setInfo(translate("updateScreen.downloading"))
|
||||
break
|
||||
case codePush.SyncStatus.INSTALLING_UPDATE:
|
||||
log.trace('Installing update...')
|
||||
setInfo('Installing update...')
|
||||
setInfo(translate("updateScreen.installing"))
|
||||
break
|
||||
}
|
||||
})
|
||||
@@ -134,7 +135,7 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
|
||||
return (
|
||||
<Screen style={$screen}>
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text preset="heading" text="Update manager" style={{color: 'white'}} />
|
||||
<Text preset="heading" tx="updateScreen.updateManagerTitle" style={{color: 'white'}} />
|
||||
</View>
|
||||
<View style={$contentContainer}>
|
||||
<Card
|
||||
@@ -145,7 +146,7 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
|
||||
<>
|
||||
<ListItem
|
||||
tx='updateScreen.updateAvailable'
|
||||
subTx={'updateScreen.updateAvailableDesc'}
|
||||
subTx='updateScreen.updateAvailableDesc'
|
||||
leftIcon='faWandMagicSparkles'
|
||||
leftIconColor={colors.palette.iconMagenta200}
|
||||
leftIconInverse={true}
|
||||
@@ -153,7 +154,7 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
|
||||
/>
|
||||
<ListItem
|
||||
tx='updateScreen.updateNew'
|
||||
subText={updateDescription || 'UX and performance improvements'}
|
||||
subText={updateDescription || translate("updateScreen.defaultUpdateDesc")}
|
||||
leftIcon='faInfoCircle'
|
||||
leftIconColor={colors.palette.neutral500}
|
||||
topSeparator={true}
|
||||
@@ -207,7 +208,7 @@ export const UpdateScreen: FC<SettingsStackScreenProps<'Update'>> = observer(fun
|
||||
/>
|
||||
<Button
|
||||
onPress={gotoGithub}
|
||||
text='APK on Github'
|
||||
tx="updateScreen.apkOnGithub"
|
||||
preset='secondary'
|
||||
style={{marginTop: spacing.medium}}
|
||||
/>
|
||||
|
||||
@@ -6,6 +6,7 @@ import Clipboard from "@react-native-clipboard/clipboard"
|
||||
import { moderateVerticalScale } from "@gocodingnow/rn-size-matters"
|
||||
import { colors, spacing } from "../../theme"
|
||||
import { useState } from "react"
|
||||
import { translate } from "../../i18n"
|
||||
|
||||
export const QRCodeBlock = function (props: {
|
||||
qrCodeData: string
|
||||
@@ -29,11 +30,11 @@ export const QRCodeBlock = function (props: {
|
||||
|
||||
if (result.action === Share.sharedAction) {
|
||||
setTimeout(
|
||||
() => infoMessage('Lightning invoice has been shared, waiting to be paid by receiver.'),
|
||||
() => infoMessage(translate('lightningInvoiceSharedWaiting')),
|
||||
500,
|
||||
)
|
||||
} else if (result.action === Share.dismissedAction) {
|
||||
infoMessage('Sharing cancelled')
|
||||
infoMessage(translate('share.cancelled'))
|
||||
}
|
||||
} catch (e: any) {
|
||||
setQrError(e)
|
||||
@@ -56,7 +57,7 @@ export const QRCodeBlock = function (props: {
|
||||
style={{backgroundColor: 'white', paddingBottom: 0}}
|
||||
ContentComponent={qrError ? (
|
||||
<ListItem
|
||||
text='Could not display QR code, copy or share the Ecash token instead.'
|
||||
text={translate("qr.fail")}
|
||||
subText={qrError ? qrError.message : ''}
|
||||
leftIcon='faTriangleExclamation'
|
||||
containerStyle={{marginVertical: spacing.large}}
|
||||
|
||||
@@ -306,7 +306,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
}
|
||||
|
||||
if (mintsStore.alreadyExists(newMintUrl)) {
|
||||
const msg = translate('mintsScreen.mintExists')
|
||||
const msg = translate('walletScreen.mintExists')
|
||||
log.info(msg)
|
||||
setInfo(msg)
|
||||
return
|
||||
@@ -530,7 +530,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
onPress={toggleSendModal}
|
||||
style={[{backgroundColor: mainButtonColor, borderWidth: 1, borderColor: screenBg}, $buttonTopup]}
|
||||
preset='tertiary'
|
||||
text='Send'
|
||||
tx='payCommon.send'
|
||||
/>
|
||||
<Button
|
||||
RightAccessory={() => (
|
||||
@@ -554,7 +554,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
/>
|
||||
)}
|
||||
onPress={toggleReceiveModal}
|
||||
text='Receive'
|
||||
tx='payCommon.receive'
|
||||
style={[{backgroundColor: mainButtonColor, borderWidth: 1, borderColor: screenBg}, $buttonPay]}
|
||||
preset='tertiary'
|
||||
/>
|
||||
@@ -576,8 +576,8 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
text='New Minibits version is available'
|
||||
subText='Updates provide new functionalities and important bug fixes. View details in the Update manager.'
|
||||
tx="updateModal.title"
|
||||
subTx="updateModal.desc"
|
||||
onPress={gotoUpdate}
|
||||
/>
|
||||
}
|
||||
@@ -591,14 +591,14 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
<>
|
||||
<ListItem
|
||||
leftIcon='faMoneyBill1'
|
||||
text='Send Ecash'
|
||||
subText='Share ecash or send it to one of your contacts'
|
||||
tx="walletScreen.sendEcash"
|
||||
subTx="walletScreen.sendEcashDesc"
|
||||
onPress={gotoSend}
|
||||
/>
|
||||
<ListItem
|
||||
leftIcon='faBolt'
|
||||
text='Pay with Lightning'
|
||||
subText='Pay invoice or to a Lightning address'
|
||||
tx="walletScreen.payWithLightning"
|
||||
subTx="walletScreen.payWithLightningDesc"
|
||||
onPress={gotoLightningPay}
|
||||
/>
|
||||
</>
|
||||
@@ -613,14 +613,14 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
<>
|
||||
<ListItem
|
||||
leftIcon='faMoneyBill1'
|
||||
text='Receive Ecash'
|
||||
subText='Paste or scan ecash token'
|
||||
tx="walletScreen.receiveEcash"
|
||||
subTx="walletScreen.receiveEcashDesc"
|
||||
onPress={gotoTokenReceive}
|
||||
/>
|
||||
<ListItem
|
||||
leftIcon='faBolt'
|
||||
text='Topup with Lightning'
|
||||
subText='Create Lightning invoice to topup your balance'
|
||||
tx='walletScreen.topupWithLightning'
|
||||
subTx="walletScreen.topupWithLightningDesc"
|
||||
onPress={gotoTopup}
|
||||
/>
|
||||
</>
|
||||
@@ -708,7 +708,7 @@ const PromoBlock = function (props: {addMint: any}) {
|
||||
<Button
|
||||
preset='default'
|
||||
onPress={props.addMint}
|
||||
text='Add your first mint'
|
||||
tx="walletScreen.addFirstMint"
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
@@ -819,7 +819,7 @@ const MintsByUnitListItem = observer(function (props: {
|
||||
{selectedMintUrl === mint.mintUrl && (
|
||||
<View style={{flexDirection: 'row', marginBottom: spacing.small, justifyContent: 'flex-start'}}>
|
||||
<Button
|
||||
text={'Topup'}
|
||||
tx="walletScreen.topup"
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faPlus'
|
||||
@@ -851,7 +851,7 @@ const MintsByUnitListItem = observer(function (props: {
|
||||
style={{minHeight: moderateVerticalScale(40), paddingVertical: moderateVerticalScale(spacing.tiny)}}
|
||||
/>*/}
|
||||
<Button
|
||||
text={'Pay'}
|
||||
tx="walletScreen.pay"
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faBolt'
|
||||
@@ -887,7 +887,7 @@ const MintsByUnitListItem = observer(function (props: {
|
||||
}}
|
||||
/>*/}
|
||||
<Button
|
||||
text={'Mint'}
|
||||
tx="wallerScreen.mintButton"
|
||||
LeftAccessory={() => (
|
||||
<View style={{marginHorizontal: spacing.extraSmall}}>
|
||||
<SvgXml
|
||||
|
||||
@@ -216,12 +216,12 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
|
||||
<Button
|
||||
onPress={gotoWallet}
|
||||
preset='secondary'
|
||||
text='Got it, take me to the wallet'
|
||||
tx="welcomeScreen.lastPageConfirmButton"
|
||||
/>
|
||||
<Button
|
||||
onPress={gotoRecovery}
|
||||
preset='tertiary'
|
||||
text='Recover lost wallet'
|
||||
tx="welcomeScreen.lastPageRecoverLostWalletButton"
|
||||
LeftAccessory={() => {return<Icon icon='faRotate'/>}}
|
||||
style={{marginTop: spacing.medium}}
|
||||
/>
|
||||
@@ -246,7 +246,7 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
|
||||
</View>
|
||||
</View>
|
||||
{error && <ErrorModal error={error} />}
|
||||
{isLoading && <Loading statusMessage={'Creating wallet seed, this takes a while...'} style={{backgroundColor: headerBg, opacity: 1}} textStyle={{color: 'white'}}/>}
|
||||
{isLoading && <Loading statusMessage={translate("welcomeScreen.generatingWalletSeedStatusMessage")} style={{backgroundColor: headerBg, opacity: 1}} textStyle={{color: 'white'}}/>}
|
||||
</Screen>
|
||||
)
|
||||
}
|
||||
|
||||
+3
-1
@@ -5,6 +5,8 @@
|
||||
"typeRoots": ["./src/utils/envtypes"],
|
||||
"paths": {
|
||||
"nostr-tools/*": ["./node_modules/nostr-tools/lib/types/*"]
|
||||
}
|
||||
},
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user