Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions src/screens/Wallet/Send/Form.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useContext, useEffect, useState } from 'react'
import { V2BrantaClient, BrantaServerBaseUrl, Payment } from '@branta-ops/branta'
import { V2BrantaClient, BrantaServerBaseUrl } from '@branta-ops/branta'
import Button from '../../../components/Button'
import ErrorMessage from '../../../components/Error'
import ButtonsOnBottom from '../../../components/ButtonsOnBottom'
Expand Down Expand Up @@ -50,6 +50,13 @@ import { AnimatePresence, motion } from 'framer-motion'
import { overlaySlideUp, overlayStyle } from '../../../lib/animations'
import { useReducedMotion } from '../../../hooks/useReducedMotion'

// TODO: Replace when SDK is accurate
type BrantaPayment = Partial<
Awaited<ReturnType<V2BrantaClient['addPayment']>>['payment'] & {
platform_logo_url: string
}
>

const brantaClient = new V2BrantaClient({
baseUrl: BrantaServerBaseUrl.Production,
})
Expand All @@ -62,8 +69,15 @@ export default function SendForm() {
const { sendInfo, setNoteInfo, setSendInfo } = useContext(FlowContext)
const { calcSubmarineSwapFee, calcArkToBtcSwapFee, createArkToBtcSwap, createSubmarineSwap, connected, getApiUrl } =
useContext(SwapsContext)
const { amountIsAboveMaxLimit, amountIsBelowMinLimit, utxoTxsAllowed, vtxoTxsAllowed, validArkToBtc } =
useContext(LimitsContext)
const {
amountIsAboveMaxLimit,
amountIsBelowMinLimit,
lnSwapsAllowed,
utxoTxsAllowed,
validArkToBtc,
validLnSwap,
vtxoTxsAllowed,
} = useContext(LimitsContext)
const { setOption } = useContext(OptionsContext)
const { navigate } = useContext(NavigationContext)
const { assetBalances, assetMetadataCache, balance, setCacheEntry, svcWallet } = useContext(WalletContext)
Expand All @@ -85,7 +99,7 @@ export default function SendForm() {
const [receivingAddresses, setReceivingAddresses] = useState<Addresses>()
const [scan, setScan] = useState(false)
const [rawScanData, setRawScanData] = useState('')
const [brantaPayment, setBrantaPayment] = useState<Payment | null>(null)
const [brantaPayment, setBrantaPayment] = useState<BrantaPayment | null>(null)
const [brantaLoading, setBrantaLoading] = useState(false)
const [selectedAsset, setSelectedAsset] = useState<AssetOption | null>(null)
const [showAssetSelector, setShowAssetSelector] = useState(false)
Expand Down Expand Up @@ -299,7 +313,7 @@ export default function SendForm() {
setBrantaLoading(true)
brantaClient
.getPaymentsByQRCode(rawScanData)
.then((payments: Payment[]) => {
.then((payments: BrantaPayment[]) => {
if (cancelled) return
const payment = payments?.[0] ?? null
if (payment) {
Expand Down Expand Up @@ -417,6 +431,7 @@ export default function SendForm() {
return
}
const satoshis = sendInfo.satoshis ?? 0
const isLightningSend = Boolean(sendInfo.invoice)
setLabel(
satoshis > liquidBalance
? 'Insufficient funds'
Expand All @@ -426,13 +441,17 @@ export default function SendForm() {
? 'Amount above LNURL max limit'
: satoshis && satoshis < 1
? 'Amount below 1 satoshi'
: amountIsAboveMaxLimit(satoshis)
? 'Amount above max limit'
: satoshis && amountIsBelowMinLimit(satoshis)
? 'Amount below min limit'
: 'Continue',
: isLightningSend && !lnSwapsAllowed()
? 'Lightning swaps not enabled'
: isLightningSend && !validLnSwap(satoshis)
? 'Amount outside Lightning swap limits'
: amountIsAboveMaxLimit(satoshis)
? 'Amount above max limit'
: satoshis && amountIsBelowMinLimit(satoshis)
? 'Amount below min limit'
: 'Continue',
)
}, [sendInfo.satoshis, sendInfo.assets, liquidBalance, selectedAsset])
}, [sendInfo.satoshis, sendInfo.assets, sendInfo.invoice, liquidBalance, selectedAsset])

// manage server unreachable error
useEffect(() => {
Expand All @@ -451,6 +470,8 @@ export default function SendForm() {
if (!sendInfo.address && !sendInfo.arkAddress && !sendInfo.invoice) return
if (sendInfo.arkAddress || sendInfo.pendingSwap) return navigate(Pages.SendDetails)
if (sendInfo.invoice) {
if (!lnSwapsAllowed()) return handleError('Lightning swaps not enabled')
if (!validLnSwap(sendInfo.satoshis ?? 0)) return handleError('Amount outside Lightning swap limits')
createSubmarineSwap(sendInfo.invoice)
Comment on lines 497 to 503
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate the actual Lightning invoice amount after fee deduction.

For LNURL fallback, amountForInvoice can be lower than satoshis when fees are deducted from the amount. The new limit checks validate satoshis, so a below-min actual swap can still proceed, or a valid post-fee swap can be rejected against the pre-fee amount.

🐛 Proposed fix
     if (sendInfo.invoice) {
       if (!lnSwapsAllowed()) return handleError('Lightning swaps not enabled')
-      if (!validLnSwap(sendInfo.satoshis ?? 0)) return handleError('Amount outside Lightning swap limits')
+      const invoiceSatoshis = getInvoiceSatoshis(sendInfo.invoice)
+      if (!invoiceSatoshis || !validLnSwap(invoiceSatoshis)) {
+        return handleError('Amount outside Lightning swap limits')
+      }
       createSubmarineSwap(sendInfo.invoice)
           // Fallback to Lightning invoice
           if (!lnSwapsAllowed()) return handleError('Lightning swaps not enabled')
-          if (!validLnSwap(satoshis)) return handleError('Amount outside Lightning swap limits')
           const amountForInvoice = deductFromAmount ? satoshis - calcSubmarineSwapFee(satoshis) : satoshis
           if (amountForInvoice < 1) return handleError('Amount too low to cover fees')
+          if (!validLnSwap(amountForInvoice)) return handleError('Amount outside Lightning swap limits')
           const invoice = await fetchInvoice(sendInfo.lnUrl, amountForInvoice, '')

Also applies to: 598-603

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/screens/Wallet/Send/Form.tsx` around lines 472 - 475, The code currently
validates the pre-fee sendInfo.satoshis but not the actual invoice amount after
LN fee deductions; change the checks so you first derive the real satoshi amount
from the invoice (e.g. const actualSats = amountForInvoice(sendInfo.invoice) ??
(sendInfo.satoshis ?? 0)) and then call validLnSwap(actualSats) and
lnSwapsAllowed() before createSubmarineSwap(sendInfo.invoice); apply the same
adjustment for the other occurrence referenced (the block around validLnSwap/use
of createSubmarineSwap at the later lines).

.then((pendingSwap) => {
if (!pendingSwap) return handleError('Unable to create swap')
Expand All @@ -468,7 +489,7 @@ export default function SendForm() {
})
.catch(() => navigate(Pages.SendDetails))
}
}, [proceed, sendInfo.address, sendInfo.arkAddress, sendInfo.invoice, sendInfo.pendingSwap])
}, [proceed, sendInfo.address, sendInfo.arkAddress, sendInfo.invoice, sendInfo.pendingSwap, sendInfo.satoshis])

// deal with fees deduction from amount
useEffect(() => {
Expand Down Expand Up @@ -574,12 +595,18 @@ export default function SendForm() {
setState({ ...sendInfo, arkAddress: arkResponse.address, invoice: undefined })
} else {
// Fallback to Lightning invoice
if (!lnSwapsAllowed()) return handleError('Lightning swaps not enabled')
if (!validLnSwap(satoshis)) return handleError('Amount outside Lightning swap limits')
const amountForInvoice = deductFromAmount ? satoshis - calcSubmarineSwapFee(satoshis) : satoshis
if (amountForInvoice < 1) return handleError('Amount too low to cover fees')
const invoice = await fetchInvoice(sendInfo.lnUrl, amountForInvoice, '')
setState({ ...sendInfo, invoice, arkAddress: undefined })
}
} else {
if (sendInfo.invoice) {
if (!lnSwapsAllowed()) return handleError('Lightning swaps not enabled')
if (!validLnSwap(satoshis)) return handleError('Amount outside Lightning swap limits')
}
setState({ ...sendInfo, satoshis })
}
setProceed(true)
Expand Down Expand Up @@ -667,6 +694,8 @@ export default function SendForm() {
: !((address || arkAddress || lnUrl || invoice) && satoshis && satoshis > 0) ||
(lnUrlLimits.max && satoshis > lnUrlLimits.max) ||
(lnUrlLimits.min && satoshis < lnUrlLimits.min) ||
(invoice && !lnSwapsAllowed()) ||
(invoice && !validLnSwap(satoshis)) ||
amountIsAboveMaxLimit(satoshis) ||
amountIsBelowMinLimit(satoshis) ||
satoshis > liquidBalance ||
Expand Down
Loading