mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
Topup and Send rewrite, fix balances
This commit is contained in:
@@ -13,7 +13,7 @@ index f2809bd..48176f1 100644
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/cashubtc/cashu-ts"
|
||||
diff --git a/node_modules/@cashu/cashu-ts/src/CashuMint.ts b/node_modules/@cashu/cashu-ts/src/CashuMint.ts
|
||||
index 1491934..7a88aea 100644
|
||||
index 1491934..7852bd1 100644
|
||||
--- a/node_modules/@cashu/cashu-ts/src/CashuMint.ts
|
||||
+++ b/node_modules/@cashu/cashu-ts/src/CashuMint.ts
|
||||
@@ -17,9 +17,9 @@ import type {
|
||||
@@ -29,6 +29,39 @@ index 1491934..7a88aea 100644
|
||||
|
||||
/**
|
||||
* Class represents Cashu Mint API. This class contains Lower level functions that are implemented by CashuWallet.
|
||||
@@ -81,6 +81,32 @@ class CashuMint {
|
||||
async mintQuote(mintQuotePayload: MintQuotePayload): Promise<MintQuoteResponse> {
|
||||
return CashuMint.mintQuote(this._mintUrl, mintQuotePayload, this._customRequest);
|
||||
}
|
||||
+ /**
|
||||
+ * Retrieve if a mint quote has been paid
|
||||
+ * @param mintUrl
|
||||
+ * @param quote Quote ID.
|
||||
+ * @param customRequest
|
||||
+ * @returns MintQuoteResponse with paid boolean
|
||||
+ */
|
||||
+ public static async mintQuotePaid(
|
||||
+ mintUrl: string,
|
||||
+ quote: string,
|
||||
+ customRequest?: typeof request
|
||||
+ ): Promise<MintQuoteResponse & {paid: boolean}> {
|
||||
+ const requestInstance = customRequest || request;
|
||||
+ return requestInstance<MintQuoteResponse & {paid: boolean}>({
|
||||
+ endpoint: joinUrls(mintUrl, '/v1/mint/quote/bolt11', quote)
|
||||
+ });
|
||||
+ }
|
||||
+
|
||||
+ /**
|
||||
+ * Retrieve if a mint quote has been paid
|
||||
+ * @param quote Quote ID.
|
||||
+ * @returns MintQuoteResponse with paid boolean
|
||||
+ */
|
||||
+ async mintQuotePaid(quote: string): Promise<MintQuoteResponse & {paid: boolean}> {
|
||||
+ return CashuMint.mintQuotePaid(this._mintUrl, quote, this._customRequest);
|
||||
+ }
|
||||
/**
|
||||
* Requests the mint to perform token minting after the LN invoice has been paid
|
||||
* @param mintUrl
|
||||
diff --git a/node_modules/@cashu/cashu-ts/src/CashuWallet.ts b/node_modules/@cashu/cashu-ts/src/CashuWallet.ts
|
||||
index 6d3a0b4..02ddb59 100644
|
||||
--- a/node_modules/@cashu/cashu-ts/src/CashuWallet.ts
|
||||
|
||||
@@ -101,6 +101,7 @@ export function Button(props: ButtonProps) {
|
||||
{ backgroundColor: defaultBg },
|
||||
preset === "secondary" && { backgroundColor: secondaryBg },
|
||||
preset === "tertiary" && { backgroundColor: tertiaryBg },
|
||||
!!LeftAccessory && {paddingLeft: spacing.tiny},
|
||||
$viewStyleOverride,
|
||||
!!pressed && [$pressedViewPresets[preset], $pressedViewStyleOverride],
|
||||
]
|
||||
@@ -141,8 +142,9 @@ const $baseViewStyle: ViewStyle = {
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexDirection: "row",
|
||||
paddingVertical: moderateVerticalScale(spacing.small),
|
||||
paddingHorizontal: spacing.small,
|
||||
paddingVertical: spacing.small,
|
||||
paddingLeft: spacing.small,
|
||||
paddingRight: spacing.small,
|
||||
overflow: "hidden",
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export const MintIcon = `<svg width="256px" height="256px" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g id="SVGRepo_bgCarrier" stroke-width="0"></g><g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g><g id="SVGRepo_iconCarrier"> <path fill-rule="evenodd" clip-rule="evenodd" d="M10.8321 1.24802C11.5779 0.917327 12.4221 0.917327 13.1679 1.24802L21.7995 5.0754C23.7751 5.95141 23.1703 9 21.0209 9H2.97906C0.829669 9 0.224891 5.9514 2.20047 5.0754L10.8321 1.24802ZM12.3893 3.12765C12.1407 3.01742 11.8593 3.01742 11.6107 3.12765L3.41076 6.76352C3.31198 6.80732 3.34324 6.95494 3.45129 6.95494H20.5487C20.6568 6.95494 20.688 6.80732 20.5892 6.76352L12.3893 3.12765Z"></path> <path d="M2 22C2 21.4477 2.44772 21 3 21H21C21.5523 21 22 21.4477 22 22C22 22.5523 21.5523 23 21 23H3C2.44772 23 2 22.5523 2 22Z"></path> <path d="M11 19C11 19.5523 11.4477 20 12 20C12.5523 20 13 19.5523 13 19V11C13 10.4477 12.5523 10 12 10C11.4477 10 11 10.4477 11 11V19Z"></path> <path d="M6 20C5.44772 20 5 19.5523 5 19L5 11C5 10.4477 5.44771 10 6 10C6.55228 10 7 10.4477 7 11L7 19C7 19.5523 6.55229 20 6 20Z"></path> <path d="M17 19C17 19.5523 17.4477 20 18 20C18.5523 20 19 19.5523 19 19V11C19 10.4477 18.5523 10 18 10C17.4477 10 17 10.4477 17 11V19Z" ></path> </g></svg>`
|
||||
@@ -1,28 +0,0 @@
|
||||
export const NutIcon = `<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!-- License: CC0. Made by SVG Repo: https://www.svgrepo.com/svg/101787/bean -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
|
||||
<g transform="translate(400, -50) rotate(75)">
|
||||
<g>
|
||||
<path d="M498.915,156.87c-13.95-28.684-38.087-47.156-69.797-53.417c-57.873-11.43-86.542,7.008-111.835,23.278
|
||||
c-18.19,11.701-33.895,21.805-61.264,21.805c-0.012,0-0.023,0-0.037,0c-27.359,0-43.07-10.106-61.259-21.807
|
||||
c-25.29-16.269-53.955-34.714-111.814-23.277c-31.728,6.263-55.875,24.742-69.829,53.441
|
||||
C-5.704,195.525-2.47,247.03,11.506,274.495c16.768,33.004,45.727,63.463,83.74,88.081
|
||||
c48.582,31.476,107.141,49.524,160.683,49.524c0.023,0,0.051,0,0.073,0c53.567-0.002,112.158-18.05,160.744-49.515
|
||||
c38.033-24.63,66.991-55.081,83.74-88.053C514.472,247.037,517.707,195.508,498.915,156.87z M470.724,259.406
|
||||
c-13.887,27.339-39.501,54.031-72.125,75.157c-43.328,28.059-95.305,44.151-142.604,44.151h-0.006c-0.023,0-0.041,0-0.065,0
|
||||
c-47.266,0-99.213-16.093-142.531-44.158c-32.608-21.118-58.222-47.818-72.129-75.192c-8.547-16.794-12.808-57.749,1.838-87.871
|
||||
c9.335-19.196,24.901-31.068,46.274-35.287c10.209-2.018,19.11-2.909,27.052-2.909c26.791,0,42.587,10.158,60.232,21.511
|
||||
c19.757,12.71,42.148,27.116,79.315,27.115c0.012-0.001,0.03,0,0.048,0c37.171,0,59.564-14.404,79.32-27.113
|
||||
c22.877-14.718,42.637-27.428,87.304-18.603c21.354,4.217,36.911,16.082,46.242,35.266
|
||||
C483.545,201.601,479.28,242.586,470.724,259.406z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g transform="translate(400, -80) rotate(80)">
|
||||
<path d="M302.428,214.517c-36.571,16.131-59.38,7.84-72.075-1.954l-20.397,26.431c15.503,11.963,33.912,17.977,54.072,17.977
|
||||
c16.349,0,33.851-3.958,51.873-11.908L302.428,214.517z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>`
|
||||
@@ -17,5 +17,5 @@ export * from "./ScanIcon"
|
||||
export * from "./BtcIcon"
|
||||
export * from "./UsdIcon"
|
||||
export * from "./EurIcon"
|
||||
export * from "./NutIcon"
|
||||
export * from "./MintIcon"
|
||||
export * from "./HeaderBg"
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
destroy,
|
||||
isStateTreeNode,
|
||||
detach,
|
||||
getSnapshot,
|
||||
} from 'mobx-state-tree'
|
||||
import {withSetPropAction} from './helpers/withSetPropAction'
|
||||
import {
|
||||
@@ -23,7 +24,8 @@ import { MINIBITS_NIP05_DOMAIN } from '@env'
|
||||
contacts: types.array(ContactModel),
|
||||
publicPubkey: types.maybe(types.string),
|
||||
lastPendingReceivedCheck: types.maybe(types.number), // UNIX timestamp
|
||||
receivedEventIds: types.optional(types.array(types.string), [])
|
||||
receivedEventIds: types.optional(types.array(types.string), []),
|
||||
selectedContact: types.maybe(types.frozen<Contact>())
|
||||
})
|
||||
.actions(withSetPropAction)
|
||||
.views(self => ({
|
||||
@@ -99,6 +101,12 @@ import { MINIBITS_NIP05_DOMAIN } from '@env'
|
||||
log.debug('[removeContact]', 'Contact removed from MintsStore')
|
||||
}
|
||||
},
|
||||
selectContact(contact: Contact) {
|
||||
self.selectedContact = getSnapshot(contact)
|
||||
},
|
||||
unselectContact() {
|
||||
self.selectedContact = undefined
|
||||
},
|
||||
removeAllContacts() {
|
||||
self.contacts.clear()
|
||||
log.debug('[removeAllContacts]', 'Removed all Contacts from ContactsStore')
|
||||
|
||||
@@ -31,9 +31,9 @@ export const PaymentRequestModel = types
|
||||
status: types.frozen<PaymentRequestStatus>(),
|
||||
mint: types.maybe(types.string),
|
||||
mintQuote: types.maybe(types.string),
|
||||
mintUnit: types.frozen<MintUnit>(), // TODO migration to set default unit for existing PRs
|
||||
mintUnit: types.maybe(types.frozen<MintUnit>()), // TODO migration to set default unit for existing PRs
|
||||
mintMethod: types.optional(types.frozen<MintMethod>(), MintMethod.BOLT11),
|
||||
amountToTopup: types.number,
|
||||
amountToTopup: types.maybe(types.number),
|
||||
encodedInvoice: types.string,
|
||||
invoicedAmount: types.number,
|
||||
invoicedUnit: types.optional(types.frozen<MintUnit>(), 'sat'),
|
||||
@@ -58,10 +58,10 @@ export type PaymentRequest = {
|
||||
type: PaymentRequestType
|
||||
status: PaymentRequestStatus
|
||||
mint?: string
|
||||
mintUnit: MintUnit
|
||||
mintUnit?: MintUnit
|
||||
mintQuote?: string
|
||||
mintMethod?: MintMethod
|
||||
amountToTopup: number
|
||||
amountToTopup?: number
|
||||
encodedInvoice: string
|
||||
invoicedAmount: number
|
||||
description?: string
|
||||
|
||||
@@ -51,6 +51,7 @@ export const ProofsStoreModel = types
|
||||
|
||||
): Proof[] | undefined {
|
||||
const proofs = options.isPending ? self.pendingProofs : self.proofs
|
||||
|
||||
if (options.unit) {
|
||||
return proofs.filter(proof => proof.mintUrl === mintUrl && proof.unit === options.unit)
|
||||
}
|
||||
@@ -188,7 +189,7 @@ export const ProofsStoreModel = types
|
||||
return self.pendingProofs
|
||||
},
|
||||
}))
|
||||
.views(self => ({ // move to MintsStore?
|
||||
.views(self => ({
|
||||
getBalances() {
|
||||
const mintBalancesMap: Map<string, MintBalance> = new Map()
|
||||
const unitBalancesMap: Map<MintUnit, number> = new Map()
|
||||
@@ -201,14 +202,21 @@ export const ProofsStoreModel = types
|
||||
for (const mint of mints) {
|
||||
const {mintUrl, units} = mint
|
||||
const zeroBalances = Object.fromEntries(units!.map(unit => [unit, 0])) as { [key in MintUnit]: number };
|
||||
mintBalancesMap.set(mintUrl, { mintUrl, balances: zeroBalances})
|
||||
mintPendingBalancesMap.set(mintUrl, { mintUrl, balances: zeroBalances})
|
||||
|
||||
// make distinct copies of zeroBalances so that balances are not summed by reference!!!
|
||||
mintBalancesMap.set(mintUrl, { mintUrl, balances: JSON.parse(JSON.stringify(zeroBalances))})
|
||||
mintPendingBalancesMap.set(mintUrl, { mintUrl, balances: JSON.parse(JSON.stringify(zeroBalances))})
|
||||
|
||||
for (const unit of mint.units!) {
|
||||
unitBalancesMap.set(unit, 0)
|
||||
}
|
||||
}
|
||||
|
||||
log.trace('[getBalances] zero balances', [...mintBalancesMap.entries()])
|
||||
log.trace('[getBalances] zero pendingBalances', [...mintPendingBalancesMap.entries()])
|
||||
|
||||
log.trace('[getBalances] proofs count', self.proofs.length)
|
||||
|
||||
for (const proof of self.proofs) {
|
||||
const { mintUrl, unit, amount } = proof
|
||||
|
||||
@@ -225,28 +233,34 @@ export const ProofsStoreModel = types
|
||||
|
||||
const mintBalances: MintBalance[] = Array.from(mintBalancesMap.values())
|
||||
|
||||
log.trace('[getBalances] filled balances', [...mintBalancesMap.entries()])
|
||||
|
||||
log.trace('[getBalances] zero pendingBalances', [...mintPendingBalancesMap.entries()])
|
||||
|
||||
// Convert map to array of UnitBalance objects
|
||||
const unitBalances: UnitBalance[] = Array.from(unitBalancesMap.entries()).map(([unit, unitBalance]) => ({
|
||||
unitBalance,
|
||||
unit
|
||||
}))
|
||||
|
||||
|
||||
|
||||
for (const proof of self.pendingProofs) {
|
||||
const { mintUrl, unit, amount } = proof
|
||||
|
||||
log.trace('adding pending proof amount', proof.amount)
|
||||
|
||||
// Make sure to not cause madness from orphaned proofs if it would happen
|
||||
if (!mintPendingBalancesMap.has(mintUrl)) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Update balance for the unit
|
||||
const mintBalance = mintPendingBalancesMap.get(mintUrl)!
|
||||
mintBalance.balances[unit] = (mintBalance.balances[unit] || 0) + amount
|
||||
const mintPendingBalance = mintPendingBalancesMap.get(mintUrl)!
|
||||
mintPendingBalance.balances[unit] = (mintPendingBalance.balances[unit] || 0) + amount
|
||||
unitPendingBalancesMap.set(unit, (unitPendingBalancesMap.get(unit) || 0) + amount)
|
||||
}
|
||||
|
||||
log.trace('[getBalances] pendingBalances', [...mintPendingBalancesMap.entries()])
|
||||
|
||||
const mintPendingBalances: MintBalance[] = Array.from(mintPendingBalancesMap.values())
|
||||
|
||||
// Convert map to array of UnitBalance objects
|
||||
@@ -324,6 +338,14 @@ export const ProofsStoreModel = types
|
||||
log.debug('[getMintBalanceWithMaxBalance]', maxBalance)
|
||||
return maxBalance
|
||||
},
|
||||
getUnitBalance: (unit: MintUnit) => {
|
||||
const balances = self.getBalances().unitBalances
|
||||
|
||||
const unitBalance = balances
|
||||
.find((balance: UnitBalance) => balance.unit === unit)
|
||||
|
||||
return unitBalance
|
||||
},
|
||||
getProofsToSend: (amount: number, proofs: Proof[]) => {
|
||||
let proofsAmount = 0
|
||||
const proofSubset = proofs.filter(proof => {
|
||||
|
||||
@@ -18,10 +18,15 @@ import {Database, MintClient} from '../services'
|
||||
import {log} from '../services/logService'
|
||||
import { getRootStore } from './helpers/getRootStore'
|
||||
import { formatDistance } from 'date-fns'
|
||||
import { MintUnit } from '../services/wallet/currency'
|
||||
import { Mint } from './Mint'
|
||||
import { ReceiveOption, SendOption } from '../screens'
|
||||
import { Contact } from './Contact'
|
||||
|
||||
export const maxTransactionsInModel = 10
|
||||
export const maxTransactionsByMint = 10
|
||||
export const maxTransactionsByHostname = 3
|
||||
export const maxTransactionsByUnit = 3
|
||||
|
||||
export type GroupedByTimeAgo = {
|
||||
[timeAgo: string]: Transaction[];
|
||||
@@ -30,6 +35,10 @@ export type GroupedByTimeAgo = {
|
||||
export const TransactionsStoreModel = types
|
||||
.model('TransactionsStore', {
|
||||
transactions: types.array(TransactionModel),
|
||||
/*tranUnit: types.maybe(types.frozen<MintUnit>()),
|
||||
tranMint: types.maybe(types.frozen<Mint>()),
|
||||
tranOption: types.maybe(types.frozen<ReceiveOption | SendOption>()),
|
||||
tranContact: types.maybe(types.frozen<Contact>())*/
|
||||
})
|
||||
.actions(withSetPropAction)
|
||||
.views(self => ({
|
||||
@@ -79,6 +88,9 @@ export const TransactionsStoreModel = types
|
||||
recentByHostname(mintHostname: string) {
|
||||
return this.all.filter(t => getHostname(t.mint as string) === mintHostname).slice(0, maxTransactionsByHostname)
|
||||
},
|
||||
recentByUnit(unit: MintUnit) {
|
||||
return this.all.filter(t => t.unit === unit).slice(0, maxTransactionsByUnit)
|
||||
},
|
||||
recentByHostnameGroupedByTimeAgo(mintHostname: string) {
|
||||
const recentByHostname = this.recentByHostname(mintHostname)
|
||||
|
||||
@@ -328,6 +340,30 @@ export const TransactionsStoreModel = types
|
||||
self.transactions.clear()
|
||||
log.debug('[removeAllTransactions]', 'Removed all transactions from TransactionsStore')
|
||||
},
|
||||
/* setTranUnit(unit: MintUnit) {
|
||||
self.tranUnit = unit
|
||||
},
|
||||
setTranMint(mint: Mint) {
|
||||
self.tranMint = mint
|
||||
},
|
||||
setTranOption(option: ReceiveOption | SendOption) {
|
||||
self.tranOption = option
|
||||
},
|
||||
setTranContact(contact: Contact) {
|
||||
self.tranContact = contact
|
||||
},
|
||||
unsetTranUnit() {
|
||||
self.tranUnit = undefined
|
||||
},
|
||||
unsetTranMint(mint: Mint) {
|
||||
self.tranMint = undefined
|
||||
},
|
||||
unsetTranOption(option: ReceiveOption | SendOption) {
|
||||
self.tranOption = undefined
|
||||
},
|
||||
unsetTranContact(contact: Contact) {
|
||||
self.tranContact = undefined
|
||||
},*/
|
||||
}))
|
||||
|
||||
const getHostname = function (mintUrl: string) {
|
||||
|
||||
@@ -120,33 +120,37 @@ export function TabsNavigator() {
|
||||
|
||||
export type WalletStackParamList = {
|
||||
Wallet: {scannedMintUrl? : string, returnWithNavigationReset?: boolean}
|
||||
ReceiveOptions: undefined
|
||||
ReceiveOptions: {unit: MintUnit}
|
||||
Receive: {encodedToken? : string}
|
||||
SendOptions: undefined
|
||||
Send: {contact?: Contact, relays?: string[], paymentOption?: SendOption}
|
||||
SendOptions: {unit: MintUnit}
|
||||
Send: {
|
||||
unit: MintUnit,
|
||||
paymentOption?: SendOption,
|
||||
contact?: Contact,
|
||||
mintUrl?: string,
|
||||
}
|
||||
Scan: undefined
|
||||
LightningPay: {
|
||||
unit: MintUnit
|
||||
mintUrl?: string,
|
||||
unit?: MintUnit
|
||||
}
|
||||
TranDetail: {id: number}
|
||||
TranHistory: undefined
|
||||
PaymentRequests: undefined
|
||||
Transfer: {
|
||||
unit: MintUnit,
|
||||
encodedInvoice?: string,
|
||||
paymentRequest?: PaymentRequest,
|
||||
lnurlParams?: LNURLPayParams & {address?: string},
|
||||
paymentOption?: SendOption,
|
||||
mintUrl?: string,
|
||||
unit?: MintUnit
|
||||
}
|
||||
Topup: {
|
||||
contact?: Contact,
|
||||
relays?: string[],
|
||||
unit: MintUnit,
|
||||
paymentOption?: ReceiveOption,
|
||||
contact?: Contact,
|
||||
lnurlParams?: LNURLWithdrawParams,
|
||||
mintUrl?: string,
|
||||
unit?: MintUnit
|
||||
}
|
||||
ContactsNavigator: {screen: string, params: any}
|
||||
SettingsNavigator: {screen: string, params: any}
|
||||
@@ -192,7 +196,7 @@ export type ContactsStackParamList = {
|
||||
WalletName: undefined
|
||||
RandomName: {navigation: any}
|
||||
OwnName: {navigation: any}
|
||||
ContactDetail: {contact: Contact, relays: string[]}
|
||||
ContactDetail: {contact: Contact}
|
||||
OwnKeys: undefined
|
||||
WalletNavigator: {screen: string, params: any}
|
||||
SettingsNavigator: {screen: string}
|
||||
|
||||
@@ -40,7 +40,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
const [info, setInfo] = useState('')
|
||||
const [isBackupModalVisible, setIsBackupModalVisible] =
|
||||
useState<boolean>(false)
|
||||
const [isHandleSpentFromSpendavleSentToQueue, setIsHandleSpentFromSpendavleSentToQueue] = useState<boolean>(false)
|
||||
const [isHandleSpentFromSpendableSentToQueue, setIsHandleSpentFromSpendableSentToQueue] = useState<boolean>(false)
|
||||
const [backupResultMessage, setBackupResultMessage] = useState<string>()
|
||||
const [totalSpentCount, setTotalSpentCount] = useState<number>(0)
|
||||
const [totalSpentAmount, setTotalSpentAmount] = useState<number>(0)
|
||||
@@ -72,7 +72,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
return () => {
|
||||
EventEmitter.off('ev__handleSpentByMintTask_result', handleSpentByMintTaskResult)
|
||||
}
|
||||
}, [isHandleSpentFromSpendavleSentToQueue])
|
||||
}, [isHandleSpentFromSpendableSentToQueue])
|
||||
|
||||
const toggleBackupSwitch = () => {
|
||||
try {
|
||||
@@ -124,7 +124,7 @@ export const BackupScreen: FC<SettingsStackScreenProps<'Backup'>> = observer(fun
|
||||
|
||||
const checkSpent = async function () {
|
||||
setIsLoading(true)
|
||||
setIsHandleSpentFromSpendavleSentToQueue(true)
|
||||
setIsHandleSpentFromSpendableSentToQueue(true)
|
||||
WalletTask.handleSpentFromSpendable()
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ interface ContactDetailScreenProps extends ContactsStackScreenProps<'ContactDeta
|
||||
|
||||
export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
function ContactScreen({route, navigation}) {
|
||||
const {contact, relays} = route.params
|
||||
const {contact} = route.params
|
||||
const {contactsStore} = useStores()
|
||||
const noteInputRef = useRef<TextInput>(null)
|
||||
|
||||
@@ -50,24 +50,24 @@ export const ContactDetailScreen: FC<ContactDetailScreenProps> = observer(
|
||||
|
||||
|
||||
const gotoTopup = () => {
|
||||
|
||||
navigation.navigate('WalletNavigator', {
|
||||
screen: 'Topup',
|
||||
params: {
|
||||
paymentOption: ReceiveOption.SEND_PAYMENT_REQUEST,
|
||||
contact,
|
||||
relays
|
||||
contact
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const gotoSend = () => {
|
||||
|
||||
navigation.navigate('WalletNavigator', {
|
||||
screen: 'Send',
|
||||
params: {
|
||||
paymentOption: SendOption.SEND_TOKEN,
|
||||
contact,
|
||||
relays
|
||||
contact
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -17,12 +17,13 @@ import { ReceiveOption } from '../ReceiveOptionsScreen'
|
||||
import { SendOption } from '../SendOptionsScreen'
|
||||
import { infoMessage, warningMessage } from '../../utils/utils'
|
||||
import { IncomingDataType, IncomingParser } from '../../services/incomingParser'
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
|
||||
|
||||
|
||||
export const PrivateContacts = observer(function (props: {
|
||||
navigation: StackNavigationProp<ContactsStackParamList, "Contacts", undefined>,
|
||||
paymentOption: ReceiveOption | SendOption | undefined}
|
||||
paymentOption: ReceiveOption | SendOption | undefined},
|
||||
) {
|
||||
const {contactsStore, relaysStore} = useStores()
|
||||
const {navigation} = props
|
||||
@@ -39,15 +40,15 @@ export const PrivateContacts = observer(function (props: {
|
||||
const { paymentOption } = props
|
||||
|
||||
if (paymentOption && paymentOption === ReceiveOption.SEND_PAYMENT_REQUEST) {
|
||||
infoMessage('Select contact to send your payment request to.')
|
||||
// infoMessage('Select contact to send your payment request to.')
|
||||
}
|
||||
|
||||
if (paymentOption && paymentOption === SendOption.SEND_TOKEN) {
|
||||
infoMessage('Select contact to send your ecash to.')
|
||||
// infoMessage('Select contact to send your ecash to.')
|
||||
}
|
||||
|
||||
if (paymentOption && paymentOption === SendOption.LNURL_ADDRESS) {
|
||||
infoMessage('Select contact to send Lightning payment to.')
|
||||
// infoMessage('Select contact to send Lightning payment to.')
|
||||
}
|
||||
}, [])
|
||||
|
||||
@@ -160,8 +161,6 @@ export const PrivateContacts = observer(function (props: {
|
||||
|
||||
log.trace('paymentOption', {paymentOption}, 'gotoContactDetail')
|
||||
|
||||
const relays = relaysStore.allUrls
|
||||
|
||||
if(paymentOption && paymentOption === ReceiveOption.SEND_PAYMENT_REQUEST) { // Topup tx contact selection
|
||||
setIsLoading(true)
|
||||
|
||||
@@ -169,19 +168,15 @@ export const PrivateContacts = observer(function (props: {
|
||||
await NostrClient.verifyNip05(contact.nip05 as string, contact.pubkey) // throws
|
||||
}
|
||||
|
||||
// contactsStore.selectContact(contact)
|
||||
navigation.navigate('WalletNavigator', {
|
||||
screen: 'Topup',
|
||||
params: {
|
||||
paymentOption,
|
||||
contact,
|
||||
relays // TODO remove, switch to relaysStore
|
||||
contact
|
||||
},
|
||||
})
|
||||
|
||||
//reset
|
||||
navigation.setParams({
|
||||
paymentOption: undefined,
|
||||
})
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
@@ -198,15 +193,10 @@ export const PrivateContacts = observer(function (props: {
|
||||
screen: 'Send',
|
||||
params: {
|
||||
paymentOption,
|
||||
contact,
|
||||
relays // TODO remove, switch to relaysStore
|
||||
contact
|
||||
},
|
||||
})
|
||||
|
||||
//reset
|
||||
navigation.setParams({
|
||||
paymentOption: undefined,
|
||||
})
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
@@ -231,10 +221,8 @@ export const PrivateContacts = observer(function (props: {
|
||||
return
|
||||
}
|
||||
|
||||
navigation.navigate('ContactDetail', {
|
||||
contact,
|
||||
relays // TODO remove, switch to relaysStore
|
||||
})
|
||||
navigation.navigate('ContactDetail', {contact})
|
||||
|
||||
} catch (e: any) {
|
||||
// reset so that invalid contact can be deleted
|
||||
navigation.setParams({
|
||||
|
||||
@@ -17,6 +17,7 @@ import { ReceiveOption } from '../ReceiveOptionsScreen'
|
||||
import { useSafeAreaInsetsStyle } from '../../utils/useSafeAreaInsetsStyle'
|
||||
import { useSafeAreaInsets } from 'react-native-safe-area-context'
|
||||
import { IncomingDataType, IncomingParser } from '../../services/incomingParser'
|
||||
import { RouteProp } from '@react-navigation/native'
|
||||
|
||||
|
||||
// const defaultPublicNpub = 'npub14n7frsyufzqsxlvkx8vje22cjah3pcwnnyqncxkuj2243jvt9kmqsdgs52'
|
||||
@@ -341,44 +342,38 @@ export const PublicContacts = observer(function (props: {
|
||||
|
||||
const gotoContactDetail = async function (contact: Contact) {
|
||||
const {paymentOption} = props
|
||||
contact.type = ContactType.PUBLIC // ???
|
||||
contact.type = ContactType.PUBLIC
|
||||
|
||||
const relays = relaysStore.allPublicUrls
|
||||
|
||||
if(paymentOption && paymentOption === ReceiveOption.SEND_PAYMENT_REQUEST) {
|
||||
if(paymentOption && paymentOption === ReceiveOption.SEND_PAYMENT_REQUEST) { // Topup tx contact selection
|
||||
/*if(contact.nip05) { // test ifworks and makes sense
|
||||
await NostrClient.verifyNip05(contact.nip05 as string, contact.pubkey) // throws
|
||||
}*/
|
||||
|
||||
if(paymentOption === ReceiveOption.SEND_PAYMENT_REQUEST) {
|
||||
navigation.navigate('WalletNavigator', {
|
||||
screen: 'Topup',
|
||||
params: {
|
||||
paymentOption,
|
||||
contact,
|
||||
relays // TODO remove, switch to relaysStore
|
||||
contact
|
||||
},
|
||||
})
|
||||
|
||||
//reset
|
||||
navigation.setParams({
|
||||
paymentOption: undefined,
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if(paymentOption && paymentOption === SendOption.SEND_TOKEN) {
|
||||
|
||||
navigation.navigate('WalletNavigator', {
|
||||
screen: 'Send',
|
||||
params: {
|
||||
paymentOption,
|
||||
contact,
|
||||
relays // TODO remove, switch to relaysStore
|
||||
contact
|
||||
},
|
||||
})
|
||||
|
||||
//reset
|
||||
navigation.setParams({
|
||||
paymentOption: undefined,
|
||||
})
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
@@ -410,8 +405,7 @@ export const PublicContacts = observer(function (props: {
|
||||
log.trace('[gotoContactDetail]', contact)
|
||||
|
||||
navigation.navigate('ContactDetail', {
|
||||
contact,
|
||||
relays // TODO remove, switch to relaysStore
|
||||
contact
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -11,22 +11,25 @@ import {useHeader} from '../utils/useHeader'
|
||||
import {log} from '../services/logService'
|
||||
import { IncomingDataType, IncomingParser } from '../services/incomingParser'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { Button, Card, ErrorModal, Icon, ListItem, ScanIcon, Screen, Text } from '../components'
|
||||
import { Button, Card, ErrorModal, Header, Icon, ListItem, ScanIcon, Screen, Text } from '../components'
|
||||
import { LnurlUtils } from '../services/lnurl/lnurlUtils'
|
||||
import { infoMessage } from '../utils/utils'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { SvgXml } from 'react-native-svg'
|
||||
import { SendOption } from './SendOptionsScreen'
|
||||
import { CurrencyCode, MintUnit } from '../services/wallet/currency'
|
||||
import { CurrencySign } from './Wallet/CurrencySign'
|
||||
import { useStores } from '../models'
|
||||
import { setMinutes } from 'date-fns'
|
||||
import { Mint } from '../models/Mint'
|
||||
import { CurrencyAmount } from './Wallet/CurrencyAmount'
|
||||
import { MintHeader } from './Mints/MintHeader'
|
||||
|
||||
|
||||
export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = function LightningPayScreen(_props) {
|
||||
const {navigation} = _props
|
||||
useHeader({
|
||||
leftIcon: 'faArrowLeft',
|
||||
onLeftPress: () => navigation.goBack(),
|
||||
})
|
||||
|
||||
const {navigation, route} = _props
|
||||
const lightningInputRef = useRef<TextInput>(null)
|
||||
const {mintsStore} = useStores()
|
||||
|
||||
useEffect(() => {
|
||||
const focus = () => {
|
||||
@@ -40,10 +43,28 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const setUnitAndMint = () => {
|
||||
if(route.params && route.params.unit && route.params.mintUrl) {
|
||||
const mint = mintsStore.findByUrl(route.params.mintUrl)
|
||||
const unit = route.params.unit
|
||||
|
||||
setMint(mint)
|
||||
setUnit(unit)
|
||||
}
|
||||
}
|
||||
|
||||
setUnitAndMint()
|
||||
|
||||
return () => {}
|
||||
}, [])
|
||||
|
||||
|
||||
|
||||
const [prevRouteName, setPrevRouteName] = useState<string>('')
|
||||
const [lightningData, setLightningData] = useState<string | undefined>(undefined)
|
||||
const [unit, setUnit] = useState<MintUnit | undefined>(undefined)
|
||||
const [mint, setMint] = useState<Mint | undefined>(undefined)
|
||||
const [error, setError] = useState<AppError | undefined>()
|
||||
|
||||
|
||||
@@ -80,7 +101,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
|
||||
try {
|
||||
const invoiceResult = IncomingParser.findAndExtract(lightningData as string, IncomingDataType.INVOICE)
|
||||
return IncomingParser.navigateWithIncomingData(invoiceResult, navigation)
|
||||
return IncomingParser.navigateWithIncomingData(invoiceResult, navigation, mint && unit && {mintUrl: mint.mintUrl, unit})
|
||||
|
||||
} catch (e: any) {
|
||||
const maybeLnurlAddress = LnurlUtils.findEncodedLnurlAddress(lightningData as string)
|
||||
@@ -93,8 +114,8 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
if(validAddress) {
|
||||
await IncomingParser.navigateWithIncomingData({
|
||||
type: IncomingDataType.LNURL_ADDRESS,
|
||||
encoded: validAddress
|
||||
}, navigation)
|
||||
encoded: validAddress,
|
||||
}, navigation, mint && unit && {mintUrl: mint.mintUrl, unit})
|
||||
}
|
||||
return
|
||||
} catch (e3: any) {
|
||||
@@ -114,7 +135,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
await IncomingParser.navigateWithIncomingData({
|
||||
type: IncomingDataType.LNURL,
|
||||
encoded: encodedLnurl
|
||||
}, navigation)
|
||||
}, navigation, mint && unit && {mintUrl: mint.mintUrl, unit})
|
||||
}
|
||||
return
|
||||
} catch (e2: any) {
|
||||
@@ -136,11 +157,20 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
}
|
||||
|
||||
const hintText = useThemeColor('textDim')
|
||||
const scanIcon = useThemeColor('text')
|
||||
const inputBg = useThemeColor('background')
|
||||
const contactIcon = useThemeColor('button')
|
||||
const headerBg = useThemeColor('header')
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<Screen preset="fixed" contentContainerStyle={$screen}>
|
||||
<MintHeader
|
||||
mint={mint}
|
||||
unit={unit}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<Text
|
||||
preset="heading"
|
||||
@@ -160,10 +190,10 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
RightComponent={
|
||||
<Button
|
||||
preset='tertiary'
|
||||
LeftAccessory={() => <Icon containerStyle={{paddingVertical: 0}} icon='faAddressBook' />}
|
||||
LeftAccessory={() => <Icon color={contactIcon} containerStyle={{paddingVertical: 0}} icon='faAddressBook' />}
|
||||
onPress={gotoContacts}
|
||||
text='Contacts'
|
||||
textStyle={{fontSize: 12, color: hintText}}
|
||||
textStyle={{fontSize: 12, color: contactIcon}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
@@ -203,7 +233,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
tx={'common.confirm'}
|
||||
onPress={onConfirm}
|
||||
style={{marginLeft: spacing.small}}
|
||||
LeftAccessory={() => <Icon icon='faCheckCircle'/>}
|
||||
LeftAccessory={() => <Icon icon='faCheckCircle' color='white'/>}
|
||||
/>
|
||||
<Button
|
||||
preset='secondary'
|
||||
@@ -216,7 +246,7 @@ export const LightningPayScreen: FC<WalletStackScreenProps<'LightningPay'>> = fu
|
||||
width={spacing.medium}
|
||||
height={spacing.medium}
|
||||
xml={ScanIcon}
|
||||
fill='white'
|
||||
fill={scanIcon}
|
||||
style={{marginHorizontal: spacing.extraSmall}}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
import React, { useEffect, useState } from "react"
|
||||
import { FlatList, LayoutAnimation, View, ViewStyle } from "react-native"
|
||||
import { observer } from "mobx-react-lite"
|
||||
import { Button, Card} from "../../components"
|
||||
import { spacing } from "../../theme"
|
||||
import { MintBalance } from "../../models/Mint"
|
||||
import { MintUnit } from "../../services/wallet/currency"
|
||||
|
||||
import { useStores } from "../../models"
|
||||
import { log } from "../../services"
|
||||
import { MintListItem } from "./MintListItem"
|
||||
|
||||
export const MintBalanceSelector = observer(function (props: {
|
||||
mintBalances: MintBalance[]
|
||||
selectedMintBalance: MintBalance
|
||||
unit: MintUnit
|
||||
title: string
|
||||
confirmTitle: string
|
||||
onMintBalanceSelect: any
|
||||
onCancel: any
|
||||
onMintBalanceConfirm: any
|
||||
}) {
|
||||
|
||||
const {mintsStore} = useStores()
|
||||
log.trace('[MintBalanceSelector]', props.selectedMintBalance.mintUrl)
|
||||
|
||||
useEffect(() => {
|
||||
if(!props.selectedMintBalance) {
|
||||
LayoutAnimation.Presets.easeInEaseOut
|
||||
setAllVisible(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const [allVisible, setAllVisible] = useState<boolean>(false)
|
||||
|
||||
const toggleAllVisible = function () {
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setAllVisible(!allVisible)
|
||||
}
|
||||
|
||||
const onMintSelect = function (balance: MintBalance) {
|
||||
log.trace('onMintBalanceSelect', balance.mintUrl)
|
||||
return props.onMintBalanceSelect(balance)
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Card
|
||||
style={$card}
|
||||
heading={props.title}
|
||||
headingStyle={{textAlign: 'center', padding: spacing.small}}
|
||||
ContentComponent={
|
||||
<>
|
||||
<FlatList<MintBalance>
|
||||
data={props.mintBalances}
|
||||
renderItem={({ item, index }) => {
|
||||
return(
|
||||
<MintListItem
|
||||
key={item.mintUrl}
|
||||
mint={mintsStore.findByUrl(item.mintUrl)!}
|
||||
mintBalance={item}
|
||||
selectedUnit={props.unit}
|
||||
onMintSelect={() => onMintSelect(item)}
|
||||
isSelectable={true}
|
||||
isSelected={props.selectedMintBalance.mintUrl === item.mintUrl}
|
||||
separator={'top'}
|
||||
style={(!allVisible && props.selectedMintBalance.mintUrl !== item.mintUrl) ? {display: 'none'} : {}}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
keyExtractor={(item) => item.mintUrl}
|
||||
style={{ flexGrow: 0, maxHeight: spacing.screenHeight * 0.35 }}
|
||||
ListFooterComponent={props.mintBalances.length > 1 ? (
|
||||
<View>
|
||||
<Button
|
||||
text={`${allVisible ? 'Hide' : 'Show'} other`}
|
||||
onPress={toggleAllVisible}
|
||||
preset='tertiary'
|
||||
/>
|
||||
</View>
|
||||
) : undefined}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<View style={$bottomContainer}>
|
||||
<View style={[$buttonContainer, {marginTop: spacing.large}]}>
|
||||
<Button
|
||||
text={props.confirmTitle}
|
||||
onPress={props.onMintBalanceConfirm}
|
||||
style={{marginRight: spacing.medium}}
|
||||
/>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.cancel'}
|
||||
onPress={props.onCancel}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
const $card: ViewStyle = {
|
||||
marginBottom: spacing.small,
|
||||
paddingTop: 0,
|
||||
}
|
||||
|
||||
|
||||
|
||||
const $buttonContainer: ViewStyle = {
|
||||
flexDirection: 'row',
|
||||
alignSelf: 'center',
|
||||
}
|
||||
|
||||
|
||||
const $bottomContainer: ViewStyle = {
|
||||
// position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
flex: 1,
|
||||
justifyContent: 'flex-end',
|
||||
marginBottom: spacing.medium,
|
||||
alignSelf: 'stretch',
|
||||
// opacity: 0,
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from "react"
|
||||
import { TextStyle, View, ViewStyle } from "react-native"
|
||||
import { Button, Header, Icon, IconTypes, ListItem, Screen, Text } from "../../components"
|
||||
import { colors, spacing, typography, useThemeColor } from "../../theme"
|
||||
import { Mint } from "../../models/Mint"
|
||||
import { MintUnit } from "../../services/wallet/currency"
|
||||
import { CurrencySign } from "../Wallet/CurrencySign"
|
||||
import { CurrencyAmount } from "../Wallet/CurrencyAmount"
|
||||
import { observer } from "mobx-react-lite"
|
||||
import { StackNavigationProp } from "@react-navigation/stack"
|
||||
|
||||
export const MintHeader = observer(function(props: {
|
||||
mint?: Mint,
|
||||
unit?: MintUnit,
|
||||
navigation: StackNavigationProp<any>
|
||||
}
|
||||
) {
|
||||
|
||||
const {mint, unit, navigation} = props
|
||||
|
||||
return (
|
||||
<Header
|
||||
TitleActionComponent={
|
||||
<>
|
||||
<Text
|
||||
text={mint && mint.shortname}
|
||||
style={{color: 'white'}}
|
||||
size='xxs'
|
||||
/>
|
||||
<CurrencySign
|
||||
mintUnit={unit && unit}
|
||||
textStyle={{color: 'white'}}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
leftIcon='faArrowLeft'
|
||||
onLeftPress={() => navigation.goBack()}
|
||||
RightActionComponent={mint && unit ? (
|
||||
<CurrencyAmount
|
||||
mintUnit={unit}
|
||||
amount={mint?.balances?.balances[unit as MintUnit] || 0}
|
||||
amountStyle={{color: 'white'}}
|
||||
symbolStyle={{color: 'white'}}
|
||||
containerStyle={{marginRight: spacing.medium}}
|
||||
size='medium'
|
||||
/>
|
||||
) : undefined}
|
||||
/>
|
||||
)
|
||||
|
||||
})
|
||||
@@ -4,7 +4,7 @@ import { ListItem, Text } from "../../components"
|
||||
import { Mint, MintStatus } from "../../models/Mint"
|
||||
import { MintBalance } from "../../models/Mint"
|
||||
import { colors, spacing, typography, useThemeColor } from "../../theme"
|
||||
import { TextStyle } from "react-native"
|
||||
import { TextStyle, ViewStyle } from "react-native"
|
||||
import { CurrencySign } from "../Wallet/CurrencySign"
|
||||
import { CurrencyCode, MintUnit, MintUnitCurrencyPairs, MintUnits } from "../../services/wallet/currency"
|
||||
import { log } from "../../services"
|
||||
@@ -18,7 +18,8 @@ export const MintListItem = observer(function(props: {
|
||||
isSelectable: boolean,
|
||||
isBlocked?: boolean,
|
||||
isUnitVisible?: boolean,
|
||||
separator: 'bottom' | 'top' | 'both' | undefined
|
||||
separator?: 'bottom' | 'top' | 'both'
|
||||
style?: ViewStyle
|
||||
}) {
|
||||
|
||||
const iconSelectedColor = useThemeColor('button')
|
||||
@@ -34,15 +35,15 @@ export const MintListItem = observer(function(props: {
|
||||
margin: spacing.extraSmall
|
||||
}
|
||||
|
||||
const {mint, mintBalance, selectedUnit, onMintSelect, isSelected, isSelectable, isBlocked, isUnitVisible, separator} = props
|
||||
const {mint, mintBalance, selectedUnit, onMintSelect, isSelected, isSelectable, isBlocked, isUnitVisible, separator, style} = props
|
||||
|
||||
// log.trace('[MintListItem]', props)
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={mint.mintUrl}
|
||||
text={mint.hostname || ''}
|
||||
subText={mint.shortname || ''}
|
||||
text={mint.shortname}
|
||||
subText={mint.hostname}
|
||||
leftIcon={isSelectable ? isSelected ? 'faCheckCircle' : 'faCircle' : undefined}
|
||||
leftIconColor={isSelected ? iconSelectedColor as string : iconColor as string}
|
||||
rightIcon={isBlocked ? 'faShieldHalved' : mint.status === MintStatus.OFFLINE ? 'faTriangleExclamation' : undefined}
|
||||
@@ -50,10 +51,10 @@ export const MintListItem = observer(function(props: {
|
||||
onPress={onMintSelect ? () => onMintSelect(mint, mintBalance) : undefined}
|
||||
RightComponent={mintBalance && selectedUnit && <Text text={`${mintBalance?.balances[selectedUnit]}`} style={{alignSelf: 'center', marginRight: spacing.medium}}/>}
|
||||
BottomComponent={isUnitVisible && mint.units ? (<>{mint.units.map(unit => <CurrencySign containerStyle={{paddingLeft: 0, marginRight: spacing.small}} key={unit} currencyCode={MintUnitCurrencyPairs[unit]}/>)}</>) : undefined}
|
||||
style={{paddingHorizontal: spacing.tiny}}
|
||||
containerStyle={{alignSelf: 'stretch'}}
|
||||
bottomSeparator={separator === 'bottom' || separator === 'both'}
|
||||
topSeparator={separator === 'top' || separator === 'both'}
|
||||
style={[{paddingHorizontal: spacing.tiny}, style]}
|
||||
/>
|
||||
)})
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ import AppError from '../utils/AppError'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
|
||||
export enum ReceiveOption {
|
||||
// CREATE_AND_SEND_PAYMENT_REQUEST = 'CREATE_AND_SEND_PAYMENT_REQUEST',
|
||||
SEND_PAYMENT_REQUEST = 'SEND_PAYMENT_REQUEST',
|
||||
PASTE_OR_SCAN_TOKEN = 'PASTE_OR_SCAN_TOKEN',
|
||||
SHOW_INVOICE = 'SHOW_INVOICE',
|
||||
@@ -41,7 +42,10 @@ export const ReceiveOptionsScreen: FC<WalletStackScreenProps<'ReceiveOptions'>>
|
||||
const gotoContacts = function () {
|
||||
navigation.navigate('ContactsNavigator', {
|
||||
screen: 'Contacts',
|
||||
params: {paymentOption: ReceiveOption.SEND_PAYMENT_REQUEST}})
|
||||
params: {
|
||||
paymentOption: ReceiveOption.SEND_PAYMENT_REQUEST
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
+118
-183
@@ -54,13 +54,15 @@ import EventEmitter from '../utils/eventEmitter'
|
||||
import {ResultModalInfo} from './Wallet/ResultModalInfo'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
import { Proof } from '../models/Proof'
|
||||
import { Contact } from '../models/Contact'
|
||||
import { Contact, ContactType } from '../models/Contact'
|
||||
import { getImageSource, infoMessage } from '../utils/utils'
|
||||
import { NotificationService } from '../services/notificationService'
|
||||
import { SendOption } from './SendOptionsScreen'
|
||||
import { moderateVerticalScale, verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { CurrencySign } from './Wallet/CurrencySign'
|
||||
import { CurrencyCode, MintUnit, MintUnits } from "../services/wallet/currency"
|
||||
import { MintHeader } from './Mints/MintHeader'
|
||||
import { MintBalanceSelector } from './Mints/MintBalanceSelector'
|
||||
|
||||
|
||||
if (Platform.OS === 'android' &&
|
||||
@@ -71,14 +73,9 @@ if (Platform.OS === 'android' &&
|
||||
export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
function SendScreen({route, navigation}) {
|
||||
|
||||
useHeader({
|
||||
leftIcon: 'faArrowLeft',
|
||||
onLeftPress: () => navigation.goBack(),
|
||||
})
|
||||
|
||||
const isInternetReachable = useIsInternetReachable()
|
||||
|
||||
const {proofsStore, walletProfileStore, transactionsStore} = useStores()
|
||||
const {proofsStore, walletProfileStore, transactionsStore, mintsStore, relaysStore} = useStores()
|
||||
const amountInputRef = useRef<TextInput>(null)
|
||||
const memoInputRef = useRef<TextInput>(null)
|
||||
|
||||
@@ -125,19 +122,40 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
}, [])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const { mintUrl, unit } = route.params
|
||||
|
||||
const setSelectedMintandUnit = () => {
|
||||
log.trace('[setSelectedMintandUnit]', mintUrl)
|
||||
setUnit(unit)
|
||||
setMintBalanceToSendFrom(proofsStore.getMintBalance(mintUrl!))
|
||||
}
|
||||
|
||||
if(mintUrl && unit) {
|
||||
setSelectedMintandUnit()
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
// Send to contact
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
|
||||
const {paymentOption} = route.params
|
||||
const {paymentOption, contact} = route.params
|
||||
|
||||
const prepareSendToken = () => {
|
||||
log.trace('prepareSendToken start')
|
||||
const prepareSendToContact = () => {
|
||||
try {
|
||||
let relays: string[] = []
|
||||
log.trace('[prepareSendToContact] selected contact', contact, paymentOption)
|
||||
|
||||
const {contact, relays} = route.params
|
||||
if(contact?.type === ContactType.PUBLIC) {
|
||||
relays = relaysStore.allPublicUrls
|
||||
} else {
|
||||
relays = relaysStore.allUrls
|
||||
}
|
||||
|
||||
if (!contact || !relays) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing contact or relay')
|
||||
if (!relays) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing NOSTR relays')
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -159,12 +177,23 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
setContactToSendTo(contact)
|
||||
setRelaysToShareTo(relays)
|
||||
|
||||
navigation.setParams({contact: undefined})
|
||||
navigation.setParams({relays: undefined})
|
||||
if(encodedTokenToSend) {
|
||||
toggleNostrDMModal() // open if we already have an invoice
|
||||
}
|
||||
|
||||
if(paymentOption && paymentOption === SendOption.SEND_TOKEN) {
|
||||
prepareSendToken()
|
||||
//reset
|
||||
navigation.setParams({
|
||||
paymentOption: undefined,
|
||||
contact: undefined
|
||||
})
|
||||
|
||||
} catch(e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
if(paymentOption && contact && paymentOption === SendOption.SEND_TOKEN) {
|
||||
prepareSendToContact()
|
||||
}
|
||||
|
||||
}, [route.params?.paymentOption])
|
||||
@@ -307,8 +336,8 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
|
||||
setAvailableMintBalances(availableBalances)
|
||||
|
||||
// Default mint with highest balance to topup
|
||||
setMintBalanceToSendFrom(availableBalances[0])
|
||||
// Default mint if not set from route params is with the one with highest balance
|
||||
if(!mintBalanceToSendFrom) {setMintBalanceToSendFrom(availableBalances[0])}
|
||||
setIsAmountEndEditing(true)
|
||||
// We do not make memo focus mandatory
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
@@ -520,6 +549,15 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
}
|
||||
|
||||
|
||||
const gotoContacts = function () {
|
||||
navigation.navigate('ContactsNavigator', {
|
||||
screen: 'Contacts',
|
||||
params: {
|
||||
paymentOption: SendOption.SEND_TOKEN
|
||||
}})
|
||||
}
|
||||
|
||||
|
||||
const resetState = function () {
|
||||
// reset state so it does not interfere next payment
|
||||
setAmountToSend('')
|
||||
@@ -555,12 +593,13 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
|
||||
return (
|
||||
<Screen preset="fixed" contentContainerStyle={$screen}>
|
||||
<MintHeader
|
||||
mint={mintBalanceToSendFrom ? mintsStore.findByUrl(mintBalanceToSendFrom?.mintUrl) : undefined}
|
||||
unit={unit}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<View style={$amountContainer}>
|
||||
<CurrencySign
|
||||
currencyCode={MintUnits[unit as any] as CurrencyCode}
|
||||
textStyle={{color: 'white'}}
|
||||
/>
|
||||
<TextInput
|
||||
ref={amountInputRef}
|
||||
onChangeText={amount => setAmountToSend(amount)}
|
||||
@@ -584,6 +623,7 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
</View>
|
||||
</View>
|
||||
<View style={$contentContainer}>
|
||||
{!encodedTokenToSend && (
|
||||
<Card
|
||||
style={$memoCard}
|
||||
ContentComponent={
|
||||
@@ -618,6 +658,7 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{!isInternetReachable && !isMemoEndEditing && (
|
||||
<Button
|
||||
preset="secondary"
|
||||
@@ -634,23 +675,29 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
|
||||
{isMintSelectorVisible && (
|
||||
<MintBalanceSelector
|
||||
availableMintBalances={availableMintBalances}
|
||||
mintBalanceToSendFrom={mintBalanceToSendFrom as MintBalance}
|
||||
mintBalances={availableMintBalances}
|
||||
selectedMintBalance={mintBalanceToSendFrom as MintBalance}
|
||||
unit={unit}
|
||||
title='Send from mint'
|
||||
confirmTitle='Create token'
|
||||
onMintBalanceSelect={onMintBalanceSelect}
|
||||
onCancel={onMintBalanceCancel}
|
||||
onMintBalanceConfirm={onMintBalanceConfirm}
|
||||
/>
|
||||
)}
|
||||
{transactionStatus === TransactionStatus.PENDING && encodedTokenToSend && paymentOption && (
|
||||
<SelectedMintBlock
|
||||
toggleNostrDMModal={toggleNostrDMModal}
|
||||
toggleQRModal={toggleQRModal}
|
||||
paymentOption={paymentOption}
|
||||
encodedTokenToSend={encodedTokenToSend}
|
||||
mintBalanceToSendFrom={mintBalanceToSendFrom as MintBalance}
|
||||
gotoWallet={resetState}
|
||||
<>
|
||||
<SendAsQRCodeBlock
|
||||
encodedTokenToSend={encodedTokenToSend as string}
|
||||
onShareToApp={onShareToApp}
|
||||
onCopy={onCopy}
|
||||
/>
|
||||
<TokenOptionsBlock
|
||||
toggleNostrDMModal={toggleNostrDMModal}
|
||||
contactToSendTo={contactToSendTo}
|
||||
gotoContacts={gotoContacts}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{(transactionStatus === TransactionStatus.COMPLETED) && (
|
||||
<View style={$bottomContainer}>
|
||||
@@ -683,20 +730,6 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
onBackButtonPress={toggleProofSelectorModal}
|
||||
onBackdropPress={toggleProofSelectorModal}
|
||||
/>
|
||||
<BottomModal
|
||||
isVisible={isQRModalVisible ? true : false}
|
||||
ContentComponent={
|
||||
<SendAsQRCodeBlock
|
||||
toggleQRModal={toggleQRModal}
|
||||
encodedTokenToSend={encodedTokenToSend as string}
|
||||
onCopy={onCopy}
|
||||
onShareToApp={onShareToApp}
|
||||
// handleError={handleError}
|
||||
/>
|
||||
}
|
||||
onBackButtonPress={toggleQRModal}
|
||||
onBackdropPress={toggleQRModal}
|
||||
/>
|
||||
<BottomModal
|
||||
isVisible={isNostrDMModalVisible ? true : false}
|
||||
ContentComponent={
|
||||
@@ -777,77 +810,6 @@ export const SendScreen: FC<WalletStackScreenProps<'Send'>> = observer(
|
||||
}
|
||||
)
|
||||
|
||||
const MintBalanceSelector = observer(function (props: {
|
||||
availableMintBalances: MintBalance[]
|
||||
mintBalanceToSendFrom: MintBalance
|
||||
unit: MintUnit
|
||||
onMintBalanceSelect: any
|
||||
onCancel: any
|
||||
onMintBalanceConfirm: any
|
||||
}) {
|
||||
|
||||
const onMintSelect = function (balance: MintBalance) {
|
||||
log.trace('onMintBalanceSelect', balance.mintUrl)
|
||||
return props.onMintBalanceSelect(balance)
|
||||
}
|
||||
|
||||
const {mintsStore} = useStores()
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Card
|
||||
style={$card}
|
||||
heading={'Pay from'}
|
||||
headingStyle={{textAlign: 'center', padding: spacing.small}}
|
||||
ContentComponent={
|
||||
<>
|
||||
<FlatList<MintBalance>
|
||||
data={props.availableMintBalances}
|
||||
renderItem={({ item, index }) => {
|
||||
return(
|
||||
<MintListItem
|
||||
key={item.mintUrl}
|
||||
mint={mintsStore.findByUrl(item.mintUrl) as Mint}
|
||||
mintBalance={item}
|
||||
selectedUnit={props.unit}
|
||||
onMintSelect={() => onMintSelect(item)}
|
||||
isSelectable={true}
|
||||
isSelected={props.mintBalanceToSendFrom.mintUrl === item.mintUrl}
|
||||
separator={'top'}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
keyExtractor={(item) => item.mintUrl}
|
||||
style={{ flexGrow: 0, maxHeight: spacing.screenHeight * 0.35 }}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<View style={$bottomContainer}>
|
||||
<View style={[$buttonContainer, {marginTop: spacing.large}]}>
|
||||
<Button
|
||||
text="Send now"
|
||||
onPress={props.onMintBalanceConfirm}
|
||||
style={{marginRight: spacing.medium}}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon="faArrowUp"
|
||||
color="white"
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
// LeftAccessory={() => <Icon icon="faCoins" color="white" size={spacing.medium} containerStyle={{marginRight: spacing.small}}/>}
|
||||
/>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.cancel'}
|
||||
onPress={props.onCancel}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
const SelectProofsBlock = observer(function (props: {
|
||||
availableMintBalances: MintBalance[]
|
||||
@@ -952,61 +914,39 @@ const SelectProofsBlock = observer(function (props: {
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
|
||||
const SelectedMintBlock = observer(function (props: {
|
||||
const TokenOptionsBlock = observer(function (props: {
|
||||
toggleNostrDMModal: any
|
||||
toggleQRModal: any
|
||||
encodedTokenToSend: string
|
||||
paymentOption: SendOption
|
||||
mintBalanceToSendFrom: MintBalance
|
||||
gotoWallet: any
|
||||
contactToSendTo?: Contact
|
||||
gotoContacts: any
|
||||
}) {
|
||||
|
||||
const {mintsStore} = useStores()
|
||||
const sendBg = useThemeColor('card')
|
||||
const tokenTextColor = useThemeColor('textDim')
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Card
|
||||
style={$card}
|
||||
heading={'Send from'}
|
||||
headingStyle={{textAlign: 'center', padding: spacing.small}}
|
||||
ContentComponent={
|
||||
<MintListItem
|
||||
mint={
|
||||
mintsStore.findByUrl(
|
||||
props.mintBalanceToSendFrom?.mintUrl as string,
|
||||
) as Mint
|
||||
}
|
||||
isSelectable={false}
|
||||
separator={'top'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<View style={$bottomContainer}>
|
||||
<View style={$buttonContainer}>
|
||||
{props.contactToSendTo ? (
|
||||
<Button
|
||||
text='QR code'
|
||||
text={`Send to ${props.contactToSendTo.nip05}`}
|
||||
preset='secondary'
|
||||
onPress={props.toggleQRModal}
|
||||
onPress={props.toggleNostrDMModal}
|
||||
style={{marginLeft: spacing.medium}}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faQrcode'
|
||||
icon='faPaperPlane'
|
||||
// color="white"
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{props.paymentOption === SendOption.SEND_TOKEN && (
|
||||
) : (
|
||||
<Button
|
||||
text='Send to contact'
|
||||
preset='secondary'
|
||||
onPress={props.toggleNostrDMModal}
|
||||
style={{marginLeft: spacing.small}}
|
||||
onPress={props.gotoContacts}
|
||||
style={{marginLeft: spacing.medium}}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faPaperPlane'
|
||||
@@ -1016,28 +956,17 @@ const SelectedMintBlock = observer(function (props: {
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
onPress={props.gotoWallet}
|
||||
style={{marginLeft: spacing.small}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const SendAsQRCodeBlock = observer(function (props: {
|
||||
toggleQRModal: any
|
||||
encodedTokenToSend: string
|
||||
onCopy: any
|
||||
onShareToApp: any
|
||||
// handleError: any
|
||||
}) {
|
||||
|
||||
const [qrError, setQrError] = useState<Error | undefined>()
|
||||
@@ -1047,10 +976,10 @@ const SendAsQRCodeBlock = observer(function (props: {
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[{marginHorizontal: spacing.small}]}>
|
||||
<Text text={'Scan to receive'} style={{alignSelf: 'center'}}/>
|
||||
|
||||
{qrError ? (
|
||||
<Card
|
||||
heading='Token to share'
|
||||
headingStyle={{textAlign: 'center'}}
|
||||
ContentComponent={qrError ? (
|
||||
<ListItem
|
||||
text='Could not display QR code, copy token instead.'
|
||||
subText={qrError ? qrError.message : ''}
|
||||
@@ -1065,31 +994,35 @@ const SendAsQRCodeBlock = observer(function (props: {
|
||||
onError={(error: any) => handleQrError(error)}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
FooterComponent={
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
text="Share"
|
||||
preset="secondary"
|
||||
onPress={props.onShareToApp}
|
||||
style={{marginRight: spacing.medium}}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon="faShareFromSquare"
|
||||
color="white"
|
||||
size={spacing.medium}
|
||||
// containerStyle={{marginRight: spacing.small}}
|
||||
textStyle={{fontSize: 14}}
|
||||
style={{
|
||||
minHeight: moderateVerticalScale(40),
|
||||
paddingVertical: moderateVerticalScale(spacing.tiny),
|
||||
marginRight: spacing.tiny
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Button preset="secondary" text="Copy" onPress={props.onCopy} />
|
||||
<Button
|
||||
preset="tertiary"
|
||||
text="Close"
|
||||
onPress={props.toggleQRModal}
|
||||
preset="secondary"
|
||||
text="Copy"
|
||||
onPress={props.onCopy}
|
||||
textStyle={{fontSize: 14}}
|
||||
style={{
|
||||
minHeight: moderateVerticalScale(40),
|
||||
paddingVertical: moderateVerticalScale(spacing.tiny),
|
||||
marginRight: spacing.tiny
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1281,6 +1214,7 @@ const $amountInput: TextStyle = {
|
||||
const $contentContainer: TextStyle = {
|
||||
flex: 1,
|
||||
padding: spacing.extraSmall,
|
||||
marginTop: -spacing.large * 2
|
||||
}
|
||||
|
||||
const $memoCard: ViewStyle = {
|
||||
@@ -1334,7 +1268,8 @@ const $qrCodeContainer: ViewStyle = {
|
||||
backgroundColor: 'white',
|
||||
padding: spacing.small,
|
||||
margin: spacing.small,
|
||||
borderRadius: spacing.small
|
||||
borderRadius: spacing.small,
|
||||
alignItems: 'center',
|
||||
}
|
||||
|
||||
const $buttonContainer: ViewStyle = {
|
||||
|
||||
+114
-168
@@ -33,26 +33,26 @@ import {
|
||||
} from '../components'
|
||||
import {TransactionStatus, Transaction} from '../models/Transaction'
|
||||
import {useStores} from '../models'
|
||||
import {useHeader} from '../utils/useHeader'
|
||||
import {NostrClient, NostrProfile, NostrUnsignedEvent, TransactionTaskResult, WalletTask} from '../services'
|
||||
import {log} from '../services/logService'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
|
||||
import {Mint, MintBalance} from '../models/Mint'
|
||||
import {MintBalance} from '../models/Mint'
|
||||
import {MintListItem} from './Mints/MintListItem'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
import {ResultModalInfo} from './Wallet/ResultModalInfo'
|
||||
import {PaymentRequest} from '../models/PaymentRequest'
|
||||
import { useFocusEffect } from '@react-navigation/native'
|
||||
import { Contact } from '../models/Contact'
|
||||
import { Contact, ContactType } from '../models/Contact'
|
||||
import { getImageSource, infoMessage } from '../utils/utils'
|
||||
import { ReceiveOption } from './ReceiveOptionsScreen'
|
||||
import { LNURLWithdrawParams } from 'js-lnurl'
|
||||
import { roundDown, roundUp, toNumber } from '../utils/number'
|
||||
import { LnurlClient, LnurlWithdrawResult } from '../services/lnurlService'
|
||||
import { moderateVerticalScale, verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { CurrencySign } from './Wallet/CurrencySign'
|
||||
import { Currencies, CurrencyCode, MintUnit, MintUnitCurrencyPairs, MintUnits } from "../services/wallet/currency"
|
||||
import { Currencies, MintUnit, MintUnitCurrencyPairs, MintUnits } from "../services/wallet/currency"
|
||||
import { MintHeader } from './Mints/MintHeader'
|
||||
import useIsInternetReachable from '../utils/useIsInternetReachable'
|
||||
import { MintBalanceSelector } from './Mints/MintBalanceSelector'
|
||||
|
||||
if (
|
||||
Platform.OS === 'android' &&
|
||||
@@ -63,12 +63,10 @@ if (
|
||||
|
||||
export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
function TopupScreen({navigation, route}) {
|
||||
useHeader({
|
||||
leftIcon: 'faArrowLeft',
|
||||
onLeftPress: () => navigation.goBack(),
|
||||
})
|
||||
|
||||
const {proofsStore, mintsStore, walletProfileStore, transactionsStore} = useStores()
|
||||
const isInternetReachable = useIsInternetReachable()
|
||||
|
||||
const {proofsStore, mintsStore, walletProfileStore, transactionsStore, relaysStore} = useStores()
|
||||
const amountInputRef = useRef<TextInput>(null)
|
||||
const memoInputRef = useRef<TextInput>(null)
|
||||
// const tokenInputRef = useRef<TextInput>(null)
|
||||
@@ -124,7 +122,7 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
const { mintUrl, unit } = route.params
|
||||
|
||||
const setSelectedMintandUnit = () => {
|
||||
setUnit(unit!)
|
||||
setUnit(unit)
|
||||
setMintBalanceToTopup(proofsStore.getMintBalance(mintUrl!))
|
||||
}
|
||||
|
||||
@@ -133,17 +131,24 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
}
|
||||
}, [])
|
||||
|
||||
// Send to contact
|
||||
// Send to contact and LNURL withdraw topup inititalization
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
const { paymentOption } = route.params
|
||||
const { paymentOption, contact } = route.params
|
||||
|
||||
const prepareSendPaymentRequest = () => {
|
||||
const prepareSendToContact = () => {
|
||||
try {
|
||||
const {contact, relays} = route.params
|
||||
let relays: string[] = []
|
||||
log.trace('[prepareSendToContact] selected contact', contact, paymentOption)
|
||||
|
||||
if (!contact || !relays) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing contact or relay')
|
||||
if(contact?.type === ContactType.PUBLIC) {
|
||||
relays = relaysStore.allPublicUrls
|
||||
} else {
|
||||
relays = relaysStore.allUrls
|
||||
}
|
||||
|
||||
if (!relays) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing NOSTR relays')
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -160,20 +165,26 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
picture
|
||||
}
|
||||
|
||||
setPaymentOption(ReceiveOption.SEND_PAYMENT_REQUEST)
|
||||
setPaymentOption(paymentOption!)
|
||||
setContactToSendFrom(contactFrom)
|
||||
setContactToSendTo(contact)
|
||||
setRelaysToShareTo(relays)
|
||||
|
||||
navigation.setParams({contact: undefined})
|
||||
navigation.setParams({relays: undefined})
|
||||
if(invoiceToPay) {
|
||||
toggleNostrDMModal() // open if we already have an invoice
|
||||
}
|
||||
|
||||
//reset
|
||||
navigation.setParams({
|
||||
paymentOption: undefined,
|
||||
contact: undefined
|
||||
})
|
||||
|
||||
} catch(e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const prepareLnurlWithdraw = () => {
|
||||
try {
|
||||
const { lnurlParams } = route.params
|
||||
@@ -187,21 +198,18 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
setLnurlWithdrawParams(lnurlParams)
|
||||
setMemo(lnurlParams.defaultDescription)
|
||||
setPaymentOption(ReceiveOption.LNURL_WITHDRAW)
|
||||
|
||||
// onAmountEndEditing(`${amountSats}`)
|
||||
} catch(e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
if(paymentOption && paymentOption === ReceiveOption.SEND_PAYMENT_REQUEST) {
|
||||
prepareSendPaymentRequest()
|
||||
if(paymentOption && contact && paymentOption === ReceiveOption.SEND_PAYMENT_REQUEST) {
|
||||
prepareSendToContact()
|
||||
}
|
||||
|
||||
if(paymentOption && paymentOption === ReceiveOption.LNURL_WITHDRAW) {
|
||||
prepareLnurlWithdraw()
|
||||
}
|
||||
|
||||
}, [route.params?.paymentOption])
|
||||
)
|
||||
|
||||
@@ -230,10 +238,6 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
return
|
||||
}
|
||||
|
||||
if (paymentOption === ReceiveOption.SHOW_INVOICE) {
|
||||
toggleQRModal()
|
||||
}
|
||||
|
||||
if (paymentOption === ReceiveOption.SEND_PAYMENT_REQUEST) {
|
||||
toggleNostrDMModal()
|
||||
}
|
||||
@@ -292,13 +296,10 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
}
|
||||
}, [transactionId])
|
||||
|
||||
|
||||
const toggleQRModal = () => setIsQRModalVisible(previousState => !previousState)
|
||||
const toggleNostrDMModal = () => setIsNostrDMModalVisible(previousState => !previousState)
|
||||
const toggleWithdrawModal = () => setIsWithdrawModalVisible(previousState => !previousState)
|
||||
const toggleResultModal = () => setIsResultModalVisible(previousState => !previousState)
|
||||
|
||||
|
||||
const onAmountEndEditing = function () {
|
||||
try {
|
||||
const precision = Currencies[MintUnitCurrencyPairs[unit]]!.precision
|
||||
@@ -327,8 +328,8 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
setAmountToTopup(`${roundUp(toNumber(amountToTopup), mantissa)}`) // round amount based on currency format
|
||||
setAvailableMintBalances(availableBalances)
|
||||
|
||||
// Default mint is the one with the highest balance to topup
|
||||
if(!mintBalanceToTopup) setMintBalanceToTopup(availableBalances[0])
|
||||
// Default mint if not set from route params is the one with the highest balance to topup
|
||||
if(!mintBalanceToTopup) {setMintBalanceToTopup(availableBalances[0])}
|
||||
setIsAmountEndEditing(true)
|
||||
// We do not make memo focus mandatory
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
@@ -492,6 +493,16 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
}
|
||||
|
||||
|
||||
const gotoContacts = function () {
|
||||
navigation.navigate('ContactsNavigator', {
|
||||
screen: 'Contacts',
|
||||
params: {
|
||||
paymentOption: ReceiveOption.SEND_PAYMENT_REQUEST
|
||||
}})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const onLnurlWithdraw = async function () {
|
||||
try {
|
||||
setIsWithdrawRequestSending(true) // replace, not working
|
||||
@@ -576,13 +587,13 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
|
||||
return (
|
||||
<Screen preset="fixed" contentContainerStyle={$screen}>
|
||||
<MintHeader
|
||||
mint={mintBalanceToTopup ? mintsStore.findByUrl(mintBalanceToTopup?.mintUrl) : undefined}
|
||||
unit={unit}
|
||||
navigation={navigation}
|
||||
/>
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<View style={$amountContainer}>
|
||||
<CurrencySign
|
||||
currencyCode={MintUnitCurrencyPairs[unit]}
|
||||
textStyle={{color: 'white'}}
|
||||
containerStyle={{alignSelf: 'center'}}
|
||||
/>
|
||||
<TextInput
|
||||
ref={amountInputRef}
|
||||
onChangeText={amount => setAmountToTopup(amount)}
|
||||
@@ -593,7 +604,9 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
keyboardType="numeric"
|
||||
selectTextOnFocus={true}
|
||||
editable={
|
||||
transactionStatus === TransactionStatus.PENDING ? false : true
|
||||
(transactionStatus === TransactionStatus.PENDING || !isInternetReachable)
|
||||
? false
|
||||
: true
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
@@ -604,6 +617,7 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
</View>
|
||||
</View>
|
||||
<View style={$contentContainer}>
|
||||
{!invoiceToPay && (
|
||||
<Card
|
||||
style={$memoCard}
|
||||
ContentComponent={
|
||||
@@ -638,27 +652,35 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isMintSelectorVisible && (
|
||||
<MintBalanceSelector
|
||||
availableMintBalances={availableMintBalances}
|
||||
mintBalanceToTopup={mintBalanceToTopup as MintBalance}
|
||||
mintBalances={availableMintBalances}
|
||||
selectedMintBalance={mintBalanceToTopup as MintBalance}
|
||||
unit={unit}
|
||||
title='Topup mint'
|
||||
confirmTitle='Create invoice'
|
||||
onMintBalanceSelect={onMintBalanceSelect}
|
||||
onCancel={onMintBalanceCancel}
|
||||
findByUrl={mintsStore.findByUrl}
|
||||
onMintBalanceConfirm={onMintBalanceConfirm}
|
||||
/>
|
||||
)}
|
||||
{transactionStatus === TransactionStatus.PENDING && invoiceToPay && paymentOption && (
|
||||
<SelectedMintBlock
|
||||
<>
|
||||
<ShareAsQRCodeBlock
|
||||
invoiceToPay={invoiceToPay as string}
|
||||
onShareToApp={onShareToApp}
|
||||
onCopy={onCopy}
|
||||
onError={handleError}
|
||||
/>
|
||||
<InvoiceOptionsBlock
|
||||
toggleNostrDMModal={toggleNostrDMModal}
|
||||
toggleQRModal={toggleQRModal}
|
||||
toggleWithdrawModal={toggleWithdrawModal}
|
||||
paymentOption={paymentOption}
|
||||
invoiceToPay={invoiceToPay}
|
||||
mintBalanceToTopup={mintBalanceToTopup as MintBalance}
|
||||
gotoWallet={resetState}
|
||||
contactToSendTo={contactToSendTo}
|
||||
gotoContacts={gotoContacts}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{(transactionStatus === TransactionStatus.COMPLETED) && (
|
||||
<View style={$bottomContainer}>
|
||||
@@ -673,7 +695,7 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
)}
|
||||
{isLoading && <Loading />}
|
||||
</View>
|
||||
<BottomModal
|
||||
{/*<BottomModal
|
||||
isVisible={isQRModalVisible}
|
||||
ContentComponent={
|
||||
<ShareAsQRCodeBlock
|
||||
@@ -686,7 +708,7 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
}
|
||||
onBackButtonPress={toggleQRModal}
|
||||
onBackdropPress={toggleQRModal}
|
||||
/>
|
||||
/>*/}
|
||||
<BottomModal
|
||||
isVisible={isNostrDMModalVisible ? true : false}
|
||||
ContentComponent={
|
||||
@@ -795,119 +817,38 @@ export const TopupScreen: FC<WalletStackScreenProps<'Topup'>> = observer(
|
||||
},
|
||||
)
|
||||
|
||||
const MintBalanceSelector = observer(function (props: {
|
||||
availableMintBalances: MintBalance[]
|
||||
mintBalanceToTopup: MintBalance
|
||||
unit: MintUnit
|
||||
onMintBalanceSelect: any
|
||||
onCancel: any
|
||||
findByUrl: any
|
||||
onMintBalanceConfirm: any
|
||||
}) {
|
||||
const onMintSelect = function (balance: MintBalance) {
|
||||
log.trace('onMintBalanceSelect', balance.mintUrl)
|
||||
return props.onMintBalanceSelect(balance)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Card
|
||||
style={$card}
|
||||
heading={'Select mint to top-up'}
|
||||
headingStyle={{textAlign: 'center', padding: spacing.small}}
|
||||
ContentComponent={
|
||||
<>
|
||||
<FlatList<MintBalance>
|
||||
data={props.availableMintBalances}
|
||||
renderItem={({ item, index }) => {
|
||||
return(
|
||||
<MintListItem
|
||||
key={item.mintUrl}
|
||||
mint={props.findByUrl(item.mintUrl)}
|
||||
mintBalance={item}
|
||||
selectedUnit={props.unit}
|
||||
onMintSelect={() => onMintSelect(item)}
|
||||
isSelectable={true}
|
||||
isSelected={props.mintBalanceToTopup.mintUrl === item.mintUrl}
|
||||
separator={'top'}
|
||||
/>
|
||||
)
|
||||
}}
|
||||
keyExtractor={(item) => item.mintUrl}
|
||||
style={{ flexGrow: 0, maxHeight: spacing.screenHeight * 0.35 }}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
/>
|
||||
<View style={$bottomContainer}>
|
||||
<View style={[$buttonContainer, {marginTop: spacing.large}]}>
|
||||
<Button
|
||||
text="Create invoice"
|
||||
onPress={props.onMintBalanceConfirm}
|
||||
style={{marginRight: spacing.medium}}
|
||||
/>
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.cancel'}
|
||||
onPress={props.onCancel}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
const SelectedMintBlock = observer(function (props: {
|
||||
const InvoiceOptionsBlock = observer(function (props: {
|
||||
toggleNostrDMModal: any
|
||||
toggleQRModal: any
|
||||
toggleWithdrawModal: any
|
||||
invoiceToPay: string
|
||||
contactToSendTo?: Contact
|
||||
paymentOption: ReceiveOption
|
||||
mintBalanceToTopup: MintBalance
|
||||
gotoWallet: any
|
||||
gotoContacts: any
|
||||
}) {
|
||||
|
||||
const {mintsStore} = useStores()
|
||||
const sendBg = useThemeColor('card')
|
||||
const tokenTextColor = useThemeColor('textDim')
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Card
|
||||
style={$card}
|
||||
heading={'Mint to topup'}
|
||||
headingStyle={{textAlign: 'center', padding: spacing.small}}
|
||||
ContentComponent={
|
||||
<MintListItem
|
||||
mint={
|
||||
mintsStore.findByUrl(
|
||||
props.mintBalanceToTopup?.mintUrl as string,
|
||||
) as Mint
|
||||
}
|
||||
isSelectable={false}
|
||||
separator={'top'}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<View style={$bottomContainer}>
|
||||
<View style={$buttonContainer}>
|
||||
{props.contactToSendTo ? (
|
||||
<Button
|
||||
text='QR code'
|
||||
text={`Send to ${props.contactToSendTo.nip05}`}
|
||||
preset='secondary'
|
||||
onPress={props.toggleQRModal}
|
||||
onPress={props.toggleNostrDMModal}
|
||||
style={{marginLeft: spacing.medium}}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faQrcode'
|
||||
icon='faPaperPlane'
|
||||
// color="white"
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{props.paymentOption === ReceiveOption.SEND_PAYMENT_REQUEST && (
|
||||
) : (
|
||||
<Button
|
||||
text='Send to contact'
|
||||
preset='secondary'
|
||||
onPress={props.toggleNostrDMModal}
|
||||
onPress={props.gotoContacts}
|
||||
style={{marginLeft: spacing.medium}}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
@@ -933,12 +874,6 @@ const SelectedMintBlock = observer(function (props: {
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
preset="secondary"
|
||||
tx={'common.close'}
|
||||
onPress={props.gotoWallet}
|
||||
style={{marginLeft: spacing.small}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
@@ -946,15 +881,16 @@ const SelectedMintBlock = observer(function (props: {
|
||||
})
|
||||
|
||||
const ShareAsQRCodeBlock = observer(function (props: {
|
||||
toggleQRModal: any
|
||||
invoiceToPay: string
|
||||
onShareToApp: any
|
||||
onCopy: any
|
||||
onError: any
|
||||
}) {
|
||||
return (
|
||||
<View style={[$bottomModal, {marginHorizontal: spacing.small}]}>
|
||||
<Text text={'Scan and pay to top-up'} />
|
||||
<Card
|
||||
heading='Invoice to pay'
|
||||
headingStyle={{textAlign: 'center'}}
|
||||
ContentComponent={
|
||||
<View style={$qrCodeContainer}>
|
||||
<QRCode
|
||||
size={270}
|
||||
@@ -962,32 +898,40 @@ const ShareAsQRCodeBlock = observer(function (props: {
|
||||
onError={props.onError}
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
FooterComponent={
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
text="Share"
|
||||
preset="secondary"
|
||||
onPress={props.onShareToApp}
|
||||
style={{marginRight: spacing.medium}}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon="faShareFromSquare"
|
||||
color="white"
|
||||
size={spacing.medium}
|
||||
// containerStyle={{marginRight: spacing.small}}
|
||||
textStyle={{fontSize: 14}}
|
||||
style={{
|
||||
minHeight: moderateVerticalScale(40),
|
||||
paddingVertical: moderateVerticalScale(spacing.tiny),
|
||||
marginRight: spacing.tiny
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
<Button preset="secondary" text="Copy" onPress={props.onCopy} />
|
||||
<Button
|
||||
preset="tertiary"
|
||||
text="Close"
|
||||
onPress={props.toggleQRModal}
|
||||
preset="secondary"
|
||||
text="Copy"
|
||||
onPress={props.onCopy}
|
||||
textStyle={{fontSize: 14}}
|
||||
style={{
|
||||
minHeight: moderateVerticalScale(40),
|
||||
paddingVertical: moderateVerticalScale(spacing.tiny),
|
||||
marginRight: spacing.tiny
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
}
|
||||
/>
|
||||
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
|
||||
const SendAsNostrDMBlock = observer(function (props: {
|
||||
toggleNostrDMModal: any
|
||||
encodedInvoiceToSend: string
|
||||
@@ -1260,6 +1204,7 @@ const $headerContainer: TextStyle = {
|
||||
const $contentContainer: TextStyle = {
|
||||
flex: 1,
|
||||
padding: spacing.extraSmall,
|
||||
marginTop: -spacing.large * 2
|
||||
}
|
||||
|
||||
const $memoCard: ViewStyle = {
|
||||
@@ -1311,9 +1256,10 @@ const $bottomModal: ViewStyle = {
|
||||
}
|
||||
|
||||
const $qrCodeContainer: ViewStyle = {
|
||||
alignItems: 'center',
|
||||
backgroundColor: 'white',
|
||||
padding: spacing.small,
|
||||
margin: spacing.small,
|
||||
margin: spacing.tiny,
|
||||
borderRadius: spacing.small
|
||||
}
|
||||
|
||||
|
||||
@@ -61,16 +61,6 @@ export const TranDetailScreen: FC<WalletStackScreenProps<'TranDetail'>> =
|
||||
observer(function TranDetailScreen(_props) {
|
||||
const {navigation, route} = _props
|
||||
const {transactionsStore, userSettingsStore} = useStores()
|
||||
useHeader({
|
||||
leftIcon: 'faArrowLeft',
|
||||
onLeftPress: () => navigation.goBack(),
|
||||
TitleActionComponent:
|
||||
<CurrencySign
|
||||
currencyCode={CurrencyCode.SATS}
|
||||
// containerStyle={{}}
|
||||
textStyle={{color: 'white'}}
|
||||
/>
|
||||
})
|
||||
|
||||
const noteInputRef = useRef<TextInput>(null)
|
||||
|
||||
@@ -85,6 +75,16 @@ export const TranDetailScreen: FC<WalletStackScreenProps<'TranDetail'>> =
|
||||
const [note, setNote] = useState<string>('')
|
||||
const [savedNote, setSavedNote] = useState<string>('')
|
||||
|
||||
useHeader({
|
||||
leftIcon: 'faArrowLeft',
|
||||
onLeftPress: () => navigation.goBack(),
|
||||
TitleActionComponent:
|
||||
<CurrencySign
|
||||
mintUnit={transaction?.unit}
|
||||
textStyle={{color: 'white'}}
|
||||
/>
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
try {
|
||||
const {id} = route.params
|
||||
@@ -254,17 +254,11 @@ export const TranDetailScreen: FC<WalletStackScreenProps<'TranDetail'>> =
|
||||
<>
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
{transaction && (
|
||||
<>
|
||||
<CurrencySign
|
||||
currencyCode={MintUnitCurrencyPairs[transaction.unit as MintUnit]}
|
||||
textStyle={{color: 'white'}}
|
||||
/>
|
||||
<Text
|
||||
preset="heading"
|
||||
text={getFormattedAmount(transaction.amount)}
|
||||
style={$tranAmount}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</View>
|
||||
<View style={$contentContainer}>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Icon, ListItem, Text } from "../../components"
|
||||
import { Transaction, TransactionStatus, TransactionType } from "../../models/Transaction"
|
||||
import { colors, spacing, typography, useThemeColor } from "../../theme"
|
||||
import useIsInternetReachable from "../../utils/useIsInternetReachable"
|
||||
import { Currencies, MintUnit, MintUnitCurrencyPairs } from '../../services/wallet/currency'
|
||||
|
||||
export interface TransactionListProps {
|
||||
transaction: Transaction,
|
||||
@@ -105,6 +106,13 @@ export const TransactionListItem = observer(function (props: TransactionListProp
|
||||
}
|
||||
|
||||
|
||||
const getAmountWithPrecision = function(tx: Transaction) {
|
||||
const unit: MintUnit = tx.unit
|
||||
const currencyPrecision: number = Currencies[MintUnitCurrencyPairs[unit]]!.precision
|
||||
return (tx.amount / currencyPrecision).toLocaleString()
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<ListItem
|
||||
key={tx.id}
|
||||
@@ -117,28 +125,28 @@ export const TransactionListItem = observer(function (props: TransactionListProp
|
||||
{([TransactionType.RECEIVE, TransactionType.RECEIVE_OFFLINE].includes(tx.type)) && (
|
||||
<>
|
||||
{[TransactionStatus.COMPLETED].includes(tx.status) && (
|
||||
<Text style={[$txAmount, {color: txReceiveColor}]}>{tx.amount.toLocaleString()}</Text>
|
||||
<Text style={[$txAmount, {color: txReceiveColor}]}>{getAmountWithPrecision(tx)}</Text>
|
||||
)}
|
||||
{[TransactionStatus.ERROR, TransactionStatus.BLOCKED, TransactionStatus.PREPARED_OFFLINE].includes(tx.status) && (
|
||||
<Text style={[$txAmount, {color: txErrorColor}]}>{tx.amount.toLocaleString()}</Text>
|
||||
<Text style={[$txAmount, {color: txErrorColor}]}>{getAmountWithPrecision(tx)}</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{([TransactionType.TOPUP].includes(tx.type)) && (
|
||||
<>
|
||||
{[TransactionStatus.PENDING, TransactionStatus.EXPIRED].includes(tx.status) && (
|
||||
<Text style={[$txAmount, {color: txPendingColor}]}>{tx.amount.toLocaleString()}</Text>
|
||||
<Text style={[$txAmount, {color: txPendingColor}]}>{getAmountWithPrecision(tx)}</Text>
|
||||
)}
|
||||
{tx.status === TransactionStatus.COMPLETED && (
|
||||
<Text style={[$txAmount, {color: txReceiveColor}]}>{tx.amount.toLocaleString()}</Text>
|
||||
<Text style={[$txAmount, {color: txReceiveColor}]}>{getAmountWithPrecision(tx)}</Text>
|
||||
)}
|
||||
{tx.status === TransactionStatus.ERROR && (
|
||||
<Text style={[$txAmount, {color: txErrorColor}]}>{tx.amount.toLocaleString()}</Text>
|
||||
<Text style={[$txAmount, {color: txErrorColor}]}>{getAmountWithPrecision(tx)}</Text>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{([TransactionType.SEND, TransactionType.TRANSFER].includes(tx.type)) && (
|
||||
<Text style={[$txAmount, {color: (tx.status === TransactionStatus.ERROR) ? txErrorColor : txSendColor}]}>-{tx.amount.toLocaleString()}</Text>
|
||||
<Text style={[$txAmount, {color: (tx.status === TransactionStatus.ERROR) ? txErrorColor : txSendColor}]}>-{getAmountWithPrecision(tx)}</Text>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
|
||||
@@ -9,24 +9,27 @@ import { verticalScale } from "@gocodingnow/rn-size-matters"
|
||||
|
||||
|
||||
export const CurrencyAmount = observer(function (props: {
|
||||
amount?: number
|
||||
amount: number
|
||||
currencyCode?: CurrencyCode,
|
||||
mintUnit?: MintUnit,
|
||||
containerStyle?: ViewStyle,
|
||||
symbolStyle?: TextStyle,
|
||||
amountStyle?: TextStyle,
|
||||
amountStyle?: TextStyle | TextStyle[],
|
||||
size?: Spacing
|
||||
})
|
||||
{
|
||||
const {currencyCode, mintUnit, amount, symbolStyle, amountStyle, containerStyle, size} = props
|
||||
let currencySymbol: string = Currencies.SATS!.symbol
|
||||
let currencyPrecision: number = Currencies.SATS!.precision
|
||||
|
||||
if(!!currencyCode) {
|
||||
currencySymbol = Currencies[currencyCode]!.symbol
|
||||
currencyPrecision = Currencies[currencyCode]!.precision
|
||||
}
|
||||
|
||||
if(!!mintUnit) {
|
||||
currencySymbol = Currencies[MintUnitCurrencyPairs[mintUnit]]!.symbol
|
||||
currencyPrecision = Currencies[MintUnitCurrencyPairs[mintUnit]]!.precision
|
||||
}
|
||||
|
||||
const amountColor = useThemeColor('amount')
|
||||
@@ -58,7 +61,7 @@ export const CurrencyAmount = observer(function (props: {
|
||||
fontSize: props.size && spacing[props.size] * 1.2 || spacing.small * 1.2,
|
||||
lineHeight: size && spacing[size] * 1.2 || spacing.small * 1.2
|
||||
}, amountStyle || {}]}
|
||||
text={`${amount || 0}`}
|
||||
text={`${amount / currencyPrecision}`}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
|
||||
+145
-74
@@ -32,7 +32,8 @@ import {
|
||||
BottomModal,
|
||||
ErrorModal,
|
||||
Header,
|
||||
ScanIcon
|
||||
ScanIcon,
|
||||
MintIcon
|
||||
} from '../components'
|
||||
import {useStores} from '../models'
|
||||
import {WalletStackScreenProps} from '../navigation'
|
||||
@@ -277,12 +278,12 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
}
|
||||
}
|
||||
|
||||
const gotoReceiveOptions = function () {
|
||||
navigation.navigate('ReceiveOptions')
|
||||
const gotoReceiveOptions = function (unit: MintUnit) {
|
||||
navigation.navigate('ReceiveOptions', {unit})
|
||||
}
|
||||
|
||||
const gotoSendOptions = function () {
|
||||
navigation.navigate('SendOptions')
|
||||
const gotoSendOptions = function (unit: MintUnit) {
|
||||
navigation.navigate('SendOptions', {unit})
|
||||
}
|
||||
|
||||
const gotoScan = function () {
|
||||
@@ -359,17 +360,27 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
leftIconColor={colors.palette.primary100}
|
||||
// style={{borderWidth: 1, borderColor: 'red'}}
|
||||
onLeftPress={gotoTranHistory}
|
||||
TitleActionComponent={
|
||||
<>
|
||||
{!isInternetReachable ? (
|
||||
TitleActionComponent={!isInternetReachable ? (
|
||||
<Text
|
||||
tx={'common.offline'}
|
||||
style={$offline}
|
||||
size='xxs'
|
||||
/>
|
||||
) : (undefined)}
|
||||
</>
|
||||
}
|
||||
) : (
|
||||
groupedMints.length > 1 ? (
|
||||
<ScalingDot
|
||||
testID={'sliding-border'}
|
||||
data={groupedMints}
|
||||
inActiveDotColor={colors.palette.primary300}
|
||||
activeDotColor={colors.palette.primary100}
|
||||
activeDotScale={1}
|
||||
containerStyle={{marginBottom: -spacing.tiny}}
|
||||
//@ts-ignore
|
||||
scrollX={scrollX}
|
||||
dotSize={25}
|
||||
/>
|
||||
) : undefined
|
||||
)}
|
||||
RightActionComponent={
|
||||
<>
|
||||
{paymentRequestsStore.countNotExpired > 0 && (
|
||||
@@ -395,7 +406,7 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
<AnimatedPagerView
|
||||
initialPage={0}
|
||||
ref={pagerRef}
|
||||
style={{flexGrow: 1, zIndex: 101, elevation: 6}}
|
||||
style={{flexGrow: 1}}
|
||||
onPageScroll={onPageScroll}
|
||||
>
|
||||
{groupedMints.map((mints) => (
|
||||
@@ -408,11 +419,37 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
mintsByUnit={mints}
|
||||
navigation={navigation}
|
||||
/>
|
||||
{/*transactionsStore.recentByHostname(mints.hostname).length > 0 && (
|
||||
{/*<View style={$buttonContainer}>
|
||||
<Button
|
||||
tx={'walletScreen.receive'}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faArrowDown'
|
||||
color='white'
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
onPress={() => gotoReceiveOptions(mints.unit)}
|
||||
style={[$buttonReceive, {borderRightColor: screenBg}]}
|
||||
/>
|
||||
<Button
|
||||
tx={'walletScreen.send'}
|
||||
RightAccessory={() => (
|
||||
<Icon
|
||||
icon='faArrowUp'
|
||||
color='white'
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
onPress={() => gotoSendOptions(mints.unit)}
|
||||
style={[$buttonSend, {borderLeftColor: screenBg}]}
|
||||
/>
|
||||
</View>*/}
|
||||
{transactionsStore.recentByUnit(mints.unit).length > 0 && (
|
||||
<Card
|
||||
ContentComponent={
|
||||
<FlatList
|
||||
data={transactionsStore.recentByHostname(mints.hostname) as Transaction[]}
|
||||
data={transactionsStore.recentByUnit(mints.unit) as Transaction[]}
|
||||
renderItem={({item, index}) => {
|
||||
return (<TransactionListItem
|
||||
key={item.id}
|
||||
@@ -430,55 +467,27 @@ export const WalletScreen: FC<WalletScreenProps> = observer(
|
||||
}
|
||||
style={[$card, {paddingTop: spacing.extraSmall}]}
|
||||
/>
|
||||
)*/}
|
||||
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
</AnimatedPagerView>
|
||||
|
||||
{isLoading && <Loading />}
|
||||
<View style={[$bottomContainer]}>
|
||||
|
||||
<View style={$buttonContainer}>
|
||||
<Button
|
||||
tx={'walletScreen.receive'}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faArrowDown'
|
||||
color='white'
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
onPress={gotoReceiveOptions}
|
||||
style={[$buttonReceive, {borderRightColor: screenBg}]}
|
||||
/>
|
||||
<View style={$bottomContainer}>
|
||||
<Button
|
||||
RightAccessory={() => (
|
||||
<SvgXml
|
||||
width={spacing.medium}
|
||||
height={spacing.medium}
|
||||
width={spacing.large + 10}
|
||||
height={spacing.large + 10}
|
||||
xml={ScanIcon}
|
||||
fill='white'
|
||||
fill={iconInfo}
|
||||
/>
|
||||
)}
|
||||
onPress={gotoScan}
|
||||
style={$buttonScan}
|
||||
/>
|
||||
<Button
|
||||
tx={'walletScreen.send'}
|
||||
RightAccessory={() => (
|
||||
<Icon
|
||||
icon='faArrowUp'
|
||||
color='white'
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
onPress={gotoSendOptions}
|
||||
style={[$buttonSend, {borderLeftColor: screenBg}]}
|
||||
preset='tertiary'
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
{isLoading && <Loading />}
|
||||
<BottomModal
|
||||
isVisible={isUpdateModalVisible ? true : false}
|
||||
style={{alignItems: 'stretch'}}
|
||||
@@ -516,19 +525,20 @@ const UnitBalanceBlock = observer(function (props: {
|
||||
const currencyColor = colors.palette.primary200
|
||||
const {unitBalance} = props
|
||||
|
||||
log.trace('[UnitBalanceBlock]', {unitBalance})
|
||||
// log.trace('[UnitBalanceBlock]', {unitBalance})
|
||||
|
||||
return (
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
<CurrencySign
|
||||
currencyCode={MintUnitCurrencyPairs[unitBalance.unit as MintUnit]}
|
||||
mintUnit={unitBalance.unit}
|
||||
size='small'
|
||||
textStyle={{color: balanceColor}}
|
||||
/>
|
||||
<Text
|
||||
preset='heading'
|
||||
style={[$unitBalance, {color: balanceColor, marginTop: spacing.small}]}
|
||||
text={unitBalance && unitBalance.unitBalance.toLocaleString() || '0'}
|
||||
<CurrencyAmount
|
||||
amount={unitBalance.unitBalance || 0}
|
||||
mintUnit={unitBalance.unit}
|
||||
symbolStyle={{display: 'none'}}
|
||||
amountStyle={[$unitBalance, {color: balanceColor, marginTop: spacing.small}]}
|
||||
/>
|
||||
</View>
|
||||
)
|
||||
@@ -612,7 +622,6 @@ const MintsByUnitListItem = observer(function (props: {
|
||||
|
||||
const gotoTopup = function (mintUrl: string, unit: MintUnit) {
|
||||
props.navigation.navigate('Topup', {
|
||||
paymentOption: ReceiveOption.SHOW_INVOICE,
|
||||
mintUrl,
|
||||
unit
|
||||
})
|
||||
@@ -627,10 +636,32 @@ const MintsByUnitListItem = observer(function (props: {
|
||||
}
|
||||
|
||||
|
||||
const gotoSend = async function (mintUrl: string, unit: MintUnit) {
|
||||
props.navigation.navigate('Send', {
|
||||
mintUrl,
|
||||
unit
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const color = useThemeColor('textDim')
|
||||
const balanceColor = useThemeColor('amount')
|
||||
const {mintsByUnit} = props
|
||||
|
||||
const getMintColor = function (unit: MintUnit) {
|
||||
if (unit === 'sat' || unit === 'msat' || unit === 'btc') {
|
||||
return colors.palette.orange400
|
||||
}
|
||||
|
||||
if (unit === 'eur') {
|
||||
return colors.palette.blue400
|
||||
}
|
||||
|
||||
if (unit === 'usd') {
|
||||
return colors.palette.green400
|
||||
}
|
||||
}
|
||||
|
||||
/* const isSingleMint: boolean = mintsByUnit.mints.length === 1 || false
|
||||
const singleMint: Mint = mintsByUnit.mints[0] */
|
||||
|
||||
@@ -645,12 +676,30 @@ const MintsByUnitListItem = observer(function (props: {
|
||||
<ListItem
|
||||
text={mint.shortname}
|
||||
subText={mint.hostname}
|
||||
leftIcon={mint.status === MintStatus.OFFLINE ? 'faTriangleExclamation' : 'faCoins'}
|
||||
leftIconInverse={true}
|
||||
leftIconColor={mint.color}
|
||||
// leftIcon={mint.status === MintStatus.OFFLINE ? 'faTriangleExclamation' : 'faMoneyBill1'}
|
||||
// leftIconInverse={true}
|
||||
// leftIconColor={mint.color}
|
||||
LeftComponent={
|
||||
<View
|
||||
style={{
|
||||
marginEnd: spacing.small,
|
||||
flex: 0,
|
||||
borderRadius: spacing.small,
|
||||
padding: spacing.extraSmall,
|
||||
backgroundColor: getMintColor(mintsByUnit.unit)
|
||||
}}
|
||||
>
|
||||
<SvgXml
|
||||
width={spacing.medium}
|
||||
height={spacing.medium}
|
||||
xml={MintIcon}
|
||||
fill='white'
|
||||
/>
|
||||
</View>
|
||||
}
|
||||
RightComponent={
|
||||
<CurrencyAmount
|
||||
amount={mint.balances?.balances[mintsByUnit.unit as MintUnit]}
|
||||
amount={mint.balances?.balances[mintsByUnit.unit as MintUnit] || 0}
|
||||
mintUnit={mintsByUnit.unit}
|
||||
size='medium'
|
||||
/>
|
||||
@@ -662,18 +711,22 @@ const MintsByUnitListItem = observer(function (props: {
|
||||
{selectedMintUrl === mint.mintUrl && (
|
||||
<View style={{flexDirection: 'row', marginBottom: spacing.small}}>
|
||||
<Button
|
||||
text={'Add funds'}
|
||||
text={'Topup'}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faPlus'
|
||||
color={color}
|
||||
size={spacing.medium}
|
||||
size={spacing.small}
|
||||
/>
|
||||
)}
|
||||
preset='tertiary'
|
||||
preset='secondary'
|
||||
textStyle={{fontSize: 14, color}}
|
||||
onPress={() => gotoTopup(mint.mintUrl, mintsByUnit.unit)}
|
||||
style={{minHeight: moderateVerticalScale(40), paddingVertical: moderateVerticalScale(spacing.tiny)}}
|
||||
style={{
|
||||
minHeight: moderateVerticalScale(40),
|
||||
paddingVertical: moderateVerticalScale(spacing.tiny),
|
||||
marginRight: spacing.tiny
|
||||
}}
|
||||
/>
|
||||
{/*<Button
|
||||
text={'Exchange'}
|
||||
@@ -690,17 +743,35 @@ const MintsByUnitListItem = observer(function (props: {
|
||||
style={{minHeight: moderateVerticalScale(40), paddingVertical: moderateVerticalScale(spacing.tiny)}}
|
||||
/>*/}
|
||||
<Button
|
||||
text={'Cash out'}
|
||||
text={'Pay'}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faArrowTurnUp'
|
||||
color={color}
|
||||
size={spacing.small}
|
||||
/>
|
||||
)}
|
||||
textStyle={{fontSize: 14, color}}
|
||||
preset='secondary'
|
||||
onPress={() => gotoLightningPay(mint.mintUrl, mintsByUnit.unit)}
|
||||
style={{
|
||||
minHeight: moderateVerticalScale(40),
|
||||
paddingVertical: moderateVerticalScale(spacing.tiny),
|
||||
marginRight: spacing.tiny
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
text={'Send'}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faMoneyBill1'
|
||||
color={color}
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
textStyle={{fontSize: 14, color}}
|
||||
preset='tertiary'
|
||||
onPress={() => gotoLightningPay(mint.mintUrl, mintsByUnit.unit)}
|
||||
preset='secondary'
|
||||
onPress={() => gotoSend(mint.mintUrl, mintsByUnit.unit)}
|
||||
style={{minHeight: moderateVerticalScale(40), paddingVertical: moderateVerticalScale(spacing.tiny)}}
|
||||
/>
|
||||
</View>
|
||||
@@ -730,13 +801,13 @@ const $headerContainer: TextStyle = {
|
||||
const $buttonContainer: ViewStyle = {
|
||||
flexDirection: 'row',
|
||||
alignSelf: 'center',
|
||||
marginTop: spacing.medium,
|
||||
marginVertical: spacing.medium,
|
||||
}
|
||||
|
||||
const $contentContainer: TextStyle = {
|
||||
marginTop: -spacing.extraLarge * 2
|
||||
marginTop: -spacing.extraLarge * 2,
|
||||
// padding: spacing.extraSmall,
|
||||
// flex: 0.82,
|
||||
flex: 1,
|
||||
// paddingTop: spacing.extraSmall - 3,
|
||||
// borderWidth: 1,
|
||||
// borderColor: 'green',
|
||||
@@ -791,10 +862,10 @@ const $balance: TextStyle = {
|
||||
}
|
||||
|
||||
const $bottomContainer: ViewStyle = {
|
||||
flex: 0.18,
|
||||
justifyContent: 'flex-start',
|
||||
// flex: 0.18,
|
||||
// justifyContent: 'flex-end',
|
||||
marginBottom: spacing.medium,
|
||||
alignSelf: 'stretch',
|
||||
alignSelf: 'center',
|
||||
// opacity: 0,
|
||||
}
|
||||
|
||||
@@ -808,7 +879,7 @@ const $buttonReceive: ViewStyle = {
|
||||
}
|
||||
|
||||
const $buttonScan: ViewStyle = {
|
||||
borderRadius: 0,
|
||||
// borderRadius: verticalScale(60 / 2),
|
||||
minWidth: verticalScale(60),
|
||||
}
|
||||
|
||||
|
||||
@@ -307,7 +307,7 @@ const getSpentOrPendingProofsFromMint = async function (
|
||||
|
||||
const spentPendingProofs = await cashuWallet.checkProofsSpent(proofs)
|
||||
|
||||
log.trace('[CashuMintClient.getSpentOrPendingProofsFromMint]', {spentPendingProofs})
|
||||
log.trace('[CashuMintClient.getSpentOrPendingProofsFromMint]', {mintUrl, spentPendingProofs})
|
||||
|
||||
return spentPendingProofs as {
|
||||
spent: CashuProof[]
|
||||
@@ -402,19 +402,22 @@ const payLightningMelt = async function (
|
||||
|
||||
|
||||
|
||||
const getLightningMintQuote = async function (
|
||||
const getBolt11MintQuote = async function (
|
||||
mintUrl: string,
|
||||
unit: MintUnit,
|
||||
amount: number,
|
||||
) {
|
||||
try {
|
||||
const cashuMint = getMint(mintUrl)
|
||||
const {request: encodedInvoice, quote: mintQuote} = await cashuMint.mintQuote({
|
||||
const {
|
||||
request: encodedInvoice,
|
||||
quote: mintQuote,
|
||||
} = await cashuMint.mintQuote({
|
||||
unit,
|
||||
amount
|
||||
})
|
||||
|
||||
log.info('[getLightningMintQuote]', {encodedInvoice, mintQuote})
|
||||
log.info('[getBolt11MintQuote]', {encodedInvoice, mintQuote})
|
||||
|
||||
return {
|
||||
encodedInvoice,
|
||||
@@ -426,7 +429,42 @@ const getLightningMintQuote = async function (
|
||||
'The mint could not return an invoice.',
|
||||
{
|
||||
message: e.message,
|
||||
caller: 'getLightningMintQuote',
|
||||
caller: 'getBolt11MintQuote',
|
||||
mintUrl,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const getBolt11MintQuoteIsPaid = async function (
|
||||
mintUrl: string,
|
||||
quote: string,
|
||||
) {
|
||||
try {
|
||||
const cashuMint = getMint(mintUrl)
|
||||
const {
|
||||
request: encodedInvoice,
|
||||
quote: mintQuote,
|
||||
paid: isPaid
|
||||
} = await cashuMint.mintQuotePaid(
|
||||
quote
|
||||
)
|
||||
|
||||
log.info('[getBolt11MintQuoteIsPaid]', {encodedInvoice, mintQuote, isPaid})
|
||||
|
||||
return {
|
||||
encodedInvoice,
|
||||
mintQuote,
|
||||
isPaid
|
||||
}
|
||||
} catch (e: any) {
|
||||
throw new AppError(
|
||||
Err.MINT_ERROR,
|
||||
'The mint could not return the state of a mint quote.',
|
||||
{
|
||||
message: e.message,
|
||||
caller: 'getBolt11MintQuoteIsPaid',
|
||||
mintUrl,
|
||||
}
|
||||
)
|
||||
@@ -557,7 +595,8 @@ export const MintClient = {
|
||||
receiveFromMint,
|
||||
sendFromMint,
|
||||
getSpentOrPendingProofsFromMint,
|
||||
getLightningMintQuote,
|
||||
getBolt11MintQuote,
|
||||
getBolt11MintQuoteIsPaid,
|
||||
mintProofs,
|
||||
getLightningMeltQuote,
|
||||
payLightningMelt,
|
||||
|
||||
@@ -6,10 +6,10 @@ import { colors } from '../theme'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { log } from './logService'
|
||||
import { CashuUtils } from './cashu/cashuUtils'
|
||||
import { MintClient, MintKeys } from './cashuMintClient'
|
||||
import { LightningUtils } from './lightning/lightningUtils'
|
||||
import { LnurlUtils } from './lnurl/lnurlUtils'
|
||||
import { LnurlClient } from './lnurlService'
|
||||
import { MintUnit } from './wallet/currency'
|
||||
|
||||
export enum IncomingDataType {
|
||||
CASHU = 'CASHU',
|
||||
@@ -135,19 +135,25 @@ const navigateWithIncomingData = async function (
|
||||
type: IncomingDataType,
|
||||
encoded: any
|
||||
},
|
||||
navigation: StackNavigationProp<any>
|
||||
navigation: StackNavigationProp<any>,
|
||||
mintAndUnit?: {
|
||||
mintUrl: string,
|
||||
unit: MintUnit
|
||||
}
|
||||
) {
|
||||
|
||||
switch (incoming.type) {
|
||||
case IncomingDataType.CASHU:
|
||||
return navigation.navigate('Receive', {
|
||||
encodedToken: incoming.encoded,
|
||||
...mintAndUnit
|
||||
})
|
||||
|
||||
case IncomingDataType.INVOICE:
|
||||
return navigation.navigate('Transfer', {
|
||||
encodedInvoice: incoming.encoded,
|
||||
paymentOption: SendOption.PASTE_OR_SCAN_INVOICE
|
||||
paymentOption: SendOption.PASTE_OR_SCAN_INVOICE,
|
||||
...mintAndUnit
|
||||
})
|
||||
|
||||
case (IncomingDataType.LNURL):
|
||||
@@ -158,14 +164,16 @@ const navigateWithIncomingData = async function (
|
||||
if(lnurlParams.tag === 'withdrawRequest') {
|
||||
return navigation.navigate('Topup', {
|
||||
lnurlParams,
|
||||
paymentOption: ReceiveOption.LNURL_WITHDRAW
|
||||
paymentOption: ReceiveOption.LNURL_WITHDRAW,
|
||||
...mintAndUnit
|
||||
})
|
||||
}
|
||||
|
||||
if(lnurlParams.tag === 'payRequest') {
|
||||
return navigation.navigate('Transfer', {
|
||||
lnurlParams,
|
||||
paymentOption: SendOption.LNURL_PAY
|
||||
paymentOption: SendOption.LNURL_PAY,
|
||||
...mintAndUnit
|
||||
})
|
||||
}
|
||||
|
||||
@@ -183,7 +191,8 @@ const navigateWithIncomingData = async function (
|
||||
|
||||
return navigation.navigate('Transfer', {
|
||||
lnurlParams: addressParamsResult.lnurlParams,
|
||||
paymentOption: SendOption.LNURL_PAY
|
||||
paymentOption: SendOption.LNURL_PAY,
|
||||
...mintAndUnit
|
||||
})
|
||||
} catch (e: any) {
|
||||
throw new AppError(Err.SERVER_ERROR, 'Could not get Lightning address details from the server.', {
|
||||
|
||||
@@ -40,7 +40,7 @@ export const sendTask = async function (
|
||||
memo: string,
|
||||
selectedProofs: Proof[]
|
||||
) : Promise<TransactionTaskResult> {
|
||||
const mintUrl = mintBalanceToSendFrom.mint
|
||||
const mintUrl = mintBalanceToSendFrom.mintUrl
|
||||
|
||||
|
||||
log.trace('[send]', 'mintBalanceToSendFrom', mintBalanceToSendFrom)
|
||||
@@ -126,7 +126,7 @@ export const sendTask = async function (
|
||||
JSON.stringify(transactionData),
|
||||
)
|
||||
|
||||
const balanceAfter = proofsStore.getBalances().totalBalance
|
||||
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance!
|
||||
|
||||
await transactionsStore.updateBalanceAfter(transactionId, balanceAfter)
|
||||
|
||||
@@ -138,7 +138,7 @@ export const sendTask = async function (
|
||||
WalletTask.handleSpentByMint,
|
||||
{
|
||||
interval: 6 * 1000,
|
||||
maxPolls: 20,
|
||||
maxPolls: 1, // TODO 10
|
||||
maxErrors: 2
|
||||
},
|
||||
{mintUrl, isPending: true}
|
||||
@@ -188,7 +188,7 @@ export const sendFromMint = async function (
|
||||
selectedProofs: Proof[],
|
||||
transactionId: number,
|
||||
) {
|
||||
const mintUrl = mintBalance.mint
|
||||
const mintUrl = mintBalance.mintUrl
|
||||
const mintInstance = mintsStore.findByUrl(mintUrl)
|
||||
|
||||
try {
|
||||
@@ -297,6 +297,7 @@ export const sendFromMint = async function (
|
||||
|
||||
// add proofs returned by the mint after the split
|
||||
if (returnedProofs.length > 0) {
|
||||
log.trace('[sendFromMint] add returned proofs to spendable')
|
||||
const { addedProofs, addedAmount } = WalletUtils.addCashuProofs(
|
||||
mintUrl,
|
||||
returnedProofs,
|
||||
@@ -309,16 +310,18 @@ export const sendFromMint = async function (
|
||||
}
|
||||
|
||||
// remove used proofs and move sent proofs to pending
|
||||
log.trace('[sendFromMint] remove proofsToSendFrom from spendable')
|
||||
proofsStore.removeProofs(proofsToSendFrom)
|
||||
|
||||
// these might be original proofToSendFrom if they matched the exact amount and split was not necessary
|
||||
log.trace('[sendFromMint] add proofsToSend to pending')
|
||||
const { addedProofs, addedAmount } = WalletUtils.addCashuProofs(
|
||||
mintUrl,
|
||||
proofsToSend,
|
||||
{
|
||||
unit,
|
||||
transactionId,
|
||||
isPending: false
|
||||
isPending: true
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { LightningUtils } from '../lightning/lightningUtils'
|
||||
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
|
||||
import { PaymentRequest, PaymentRequestStatus, PaymentRequestType } from '../../models/PaymentRequest'
|
||||
import { WalletUtils } from './utils'
|
||||
import { MintUnit } from './currency'
|
||||
import { MintUnit, MintUnits } from './currency'
|
||||
|
||||
const {
|
||||
transactionsStore,
|
||||
@@ -59,7 +59,7 @@ export const topupTask = async function (
|
||||
const storedTransaction: TransactionRecord = await transactionsStore.addTransaction(newTransaction)
|
||||
transactionId = storedTransaction.id as number
|
||||
|
||||
const {encodedInvoice, mintQuote} = await MintClient.getLightningMintQuote(mintUrl, unit, amountToTopup)
|
||||
const {encodedInvoice, mintQuote} = await MintClient.getBolt11MintQuote(mintUrl, unit, amountToTopup)
|
||||
|
||||
const decodedInvoice = LightningUtils.decodeInvoice(encodedInvoice)
|
||||
const {amount, payment_hash, expiry, timestamp} = LightningUtils.getInvoiceData(decodedInvoice)
|
||||
@@ -104,6 +104,7 @@ export const topupTask = async function (
|
||||
amountToTopup,
|
||||
encodedInvoice,
|
||||
invoicedAmount: amount,
|
||||
invoicedUnit: 'sat',
|
||||
description: memo ? memo : contactTo ? `Pay to ${walletProfileStore.nip05}` : '',
|
||||
paymentHash: payment_hash,
|
||||
contactFrom,
|
||||
@@ -132,8 +133,8 @@ export const topupTask = async function (
|
||||
WalletTask.handlePendingTopup,
|
||||
{
|
||||
interval: 6 * 1000,
|
||||
maxPolls: 1, // TODO 20!!!
|
||||
maxErrors: 5
|
||||
maxPolls: 10,
|
||||
maxErrors: 2
|
||||
},
|
||||
{paymentRequest})
|
||||
.then(() => log.trace('Polling completed', [], `handlePendingTopupTaskPoller`))
|
||||
|
||||
@@ -30,7 +30,7 @@ import { topupTask } from './wallet/topupTask'
|
||||
import { transferTask } from './wallet/transferTask'
|
||||
import { WalletUtils } from './wallet/utils'
|
||||
import { NotificationService } from './notificationService'
|
||||
import { MintUnit } from './wallet/currency'
|
||||
import { Currencies, CurrencyCode, MintUnit, MintUnitCurrencyPairs, MintUnits } from './wallet/currency'
|
||||
|
||||
|
||||
type WalletTaskService = {
|
||||
@@ -296,7 +296,7 @@ const handleSpentByMint = async function (
|
||||
}
|
||||
): Promise<void> {
|
||||
const {mintUrl, isPending} = options
|
||||
log.trace('[handleSpentByMint] start')
|
||||
log.trace('[handleSpentByMint] start', {mintUrl, isPending})
|
||||
const now = new Date().getTime()
|
||||
|
||||
return SyncQueue.addTask(
|
||||
@@ -378,7 +378,7 @@ const _handleSpentByMintTask = async function (
|
||||
pendingCount = pendingProofs.length
|
||||
pendingAmount = CashuUtils.getProofsAmount(pendingProofs as Proof[])
|
||||
|
||||
log.trace('[_handleSpentByMintTask]', `${isPending ? 'Pending' : ''} spents and pending by mint amounts`, {spentAmount, pendingAmount, isPending})
|
||||
log.trace('[_handleSpentByMintTask]', `${isPending ? 'Pending' : ''} spent and pending by mint amounts`, {spentAmount, pendingAmount, isPending})
|
||||
|
||||
if (spentCount > 0) {
|
||||
// identify txIds to update their statuses, there might be more then one tx to complete
|
||||
@@ -699,9 +699,10 @@ const _handleInFlightByMintTask = async function (mint: Mint, seed: Uint8Array):
|
||||
|
||||
|
||||
const handlePendingTopups = async function (): Promise<void> {
|
||||
log.trace('[handlePendingTopups] start')
|
||||
const paymentRequests: PaymentRequest[] = paymentRequestsStore.allOutgoing
|
||||
|
||||
log.trace('[handlePendingTopups] start', {paymentRequests})
|
||||
|
||||
if (paymentRequests.length === 0) {
|
||||
log.trace('[handlePendingTopups]', 'No outgoing payment requests in store - skipping task send to the queue...')
|
||||
return
|
||||
@@ -743,15 +744,15 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
const {paymentRequest: pr} = params
|
||||
const transactionId = {...pr}.transactionId || 0// copy
|
||||
const mint = {...pr}.mint // copy
|
||||
const unit = {...pr}.unit // copy
|
||||
const amount = {...pr}.amount // copy
|
||||
const unit = {...pr}.mintUnit // copy, unit of proofs to be received
|
||||
const amount = {...pr}.amountToTopup // copy, amount of proofs to be received
|
||||
const paymentHash = {...pr}.paymentHash // copy
|
||||
const mintQuote = {...pr}.mintQuote // copy
|
||||
const mintInstance = mintsStore.findByUrl(mint as string)
|
||||
|
||||
try {
|
||||
if(!mintInstance) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint', {mintUrl: mint})
|
||||
if(!mintInstance || !mintQuote || !unit || !amount) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing mint or mint quote or mintUnit or amountToTopup', {mintUrl: mint})
|
||||
}
|
||||
|
||||
const amountPreferences = getDefaultAmountPreference(amount)
|
||||
@@ -761,6 +762,25 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
log.trace('[_handlePendingTopupTask]', 'amountPreferences', amountPreferences)
|
||||
log.trace('[_handlePendingTopupTask]', 'countOfInFlightProofs', countOfInFlightProofs)
|
||||
|
||||
// check is quote has been paid
|
||||
const { isPaid, mintQuote: quote } = await MintClient.getBolt11MintQuoteIsPaid(mint!, mintQuote)
|
||||
|
||||
if (quote !== mintQuote) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Returned quote is different then the one requested', {mintUrl: mint, quote, mintQuote})
|
||||
}
|
||||
|
||||
if (isPaid !== true) {
|
||||
log.trace('[_handlePendingTopupTask] Quot not paid', {mintUrl: mint, mintQuote})
|
||||
return {
|
||||
taskFunction: '_handlePendingTopupTask',
|
||||
mintUrl: mint,
|
||||
unit,
|
||||
amount,
|
||||
paymentHash,
|
||||
message: `Quote ${mintQuote} not paid`,
|
||||
} as WalletTaskResult
|
||||
}
|
||||
|
||||
// temp increase the counter + acquire lock and set inFlight values
|
||||
await WalletUtils.lockAndSetInFlight(
|
||||
mintInstance,
|
||||
@@ -784,7 +804,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
lockedProofsCounter.inFlightFrom as number
|
||||
)) as CashuProof[]
|
||||
|
||||
log.info('[_handlePendingTopupTask]', {proofs})
|
||||
// log.info('[_handlePendingTopupTask]', {proofs})
|
||||
|
||||
} catch (e: any) {
|
||||
// decrease so that unpaid invoices does not cause counter gaps from polling
|
||||
@@ -880,10 +900,12 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
mintInstance.resetInFlight(transactionId as number )
|
||||
stopPolling(`handlePendingTopupTaskPoller-${paymentHash}`)
|
||||
|
||||
if (receivedAmount !== pr.amount) {
|
||||
const precision: number = Currencies[MintUnitCurrencyPairs[pr.mintUnit as MintUnit]]!.precision
|
||||
|
||||
if (receivedAmount !== amount) {
|
||||
throw new AppError(
|
||||
Err.VALIDATION_ERROR,
|
||||
`Received amount ${receivedAmount} SATS is not equal to the requested amount ${amount} SATS.`,
|
||||
`Received amount ${receivedAmount / precision} ${unit} is not equal to the requested amount ${amount / precision} ${unit}.`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -908,8 +930,8 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
// Fire event that the TopupScreen can listen to // TODO replace by standard task result event
|
||||
// EventEmitter.emit('ev_topupCompleted', {...pr})
|
||||
|
||||
// Update tx with current balance
|
||||
const balanceAfter = proofsStore.getBalances().mintBalances[mint as any].balances[unit as MintUnit]!
|
||||
// Update tx with current total balance of topup unit/currency
|
||||
const balanceAfter = proofsStore.getUnitBalance(unit)?.unitBalance!
|
||||
|
||||
await transactionsStore.updateBalanceAfter(
|
||||
transactionId as number,
|
||||
@@ -919,6 +941,8 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
_sendTopupNotification(pr)
|
||||
paymentRequestsStore.removePaymentRequest(pr)
|
||||
|
||||
|
||||
|
||||
return {
|
||||
taskFunction: '_handlePendingTopupTask',
|
||||
mintUrl: mint,
|
||||
@@ -926,7 +950,7 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
amount,
|
||||
paymentHash,
|
||||
transaction: transactionsStore.findById(transactionId as number),
|
||||
message: `Your invoice has been paid and your wallet balance credited with ${amount} SATS.`,
|
||||
message: `Your invoice has been paid and your wallet balance credited with ${amount / precision} ${unit}.`,
|
||||
} as TransactionTaskResult
|
||||
|
||||
} catch (e: any) {
|
||||
@@ -948,10 +972,15 @@ const _handlePendingTopupTask = async function (params: {paymentRequest: Payment
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const _sendTopupNotification = async function (pr: PaymentRequest) {
|
||||
|
||||
const precision: number = Currencies[MintUnitCurrencyPairs[pr.mintUnit as MintUnit]]!.precision
|
||||
|
||||
await NotificationService.createLocalNotification(
|
||||
`⚡ ${pr.amount} SATS received!`,
|
||||
`Your invoice has been paid and your wallet balance credited with ${pr.amount} SATS.`,
|
||||
`⚡ ${pr.amountToTopup! / precision} ${pr.mintUnit} received!`,
|
||||
`Your invoice has been paid and your wallet balance credited with ${pr.amountToTopup! / precision} ${pr.mintUnit}.`,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1174,8 +1203,8 @@ const _handleReceivedEventTask = async function (event: NostrEvent): Promise<Wal
|
||||
type: PaymentRequestType.INCOMING,
|
||||
status: PaymentRequestStatus.ACTIVE,
|
||||
encodedInvoice: incoming.encoded,
|
||||
unit: 'sat', // bolt11
|
||||
amount: amount || 0,
|
||||
invoicedUnit: 'sat', // bolt11
|
||||
invoicedAmount: amount || 0,
|
||||
description: maybeMemo ? maybeMemo : description,
|
||||
paymentHash,
|
||||
contactFrom: contactFrom || {pubkey: sentFromPubkey, npub: sentFromNpub},
|
||||
@@ -1276,7 +1305,7 @@ const _sendReceiveNotification = async function (
|
||||
|
||||
const _sendPaymentRequestNotification = async function (pr: PaymentRequest) {
|
||||
await NotificationService.createLocalNotification(
|
||||
`⚡ Please pay ${pr.amount} SATS!`,
|
||||
`⚡ Please pay ${pr.invoicedAmount} ${pr.invoicedUnit}!`,
|
||||
`${pr.contactFrom.nip05 || 'Unknown'} has sent you a request to pay an invoice.`,
|
||||
pr.contactFrom.picture,
|
||||
)
|
||||
|
||||
+4
-1
@@ -44,9 +44,12 @@ const palette = {
|
||||
|
||||
blue100: '#75B4FB',
|
||||
blue200: '#818AFF',
|
||||
blue400: '#2d57e1',
|
||||
|
||||
green400: '#599D52',
|
||||
|
||||
orange200: '#FFAB51',
|
||||
orange400: '#F7931A',
|
||||
orange400: '#FF9900',
|
||||
|
||||
focus100: '#FB4E9E',
|
||||
focus200: '#F62586',
|
||||
|
||||
Reference in New Issue
Block a user