Add branta (#845)

* adding translations

* adding branta

* implemtn branta with pollyfill

* improve pollyfill

* change function name
This commit is contained in:
Blake Kaufman
2026-05-17 13:30:09 -04:00
committed by GitHub
parent f9535e9ba0
commit 24b07d26d2
15 changed files with 193 additions and 68 deletions
@@ -16,6 +16,7 @@ export default function InvoiceInfo({
darkModeType,
isSplitPayment,
splitRecipients = [],
isUsingBranta,
}) {
const formmateedSparkPaymentInfo = formatSparkPaymentAddress(
paymentInfo,
@@ -40,7 +41,30 @@ export default function InvoiceInfo({
]}
disabled={isSplitPayment}
>
{isSplitPayment ? (
{isUsingBranta ? (
<View style={styles.contactRow}>
<View
style={[
styles.profileImage,
{
backgroundColor: backgroundColor,
},
]}
>
<ContactProfileImage
updated={undefined}
uri={paymentInfo?.brantaMerchantLogo}
darkModeType={darkModeType}
theme={theme}
/>
</View>
<ThemeText
styles={styles.addressText}
CustomNumberOfLines={1}
content={paymentInfo?.brantaMerchantName || ''}
/>
</View>
) : isSplitPayment ? (
<ProfileImageRow
avatarSize={40}
contacts={splitContacts}
@@ -21,6 +21,9 @@ import { getCachedProfileImage } from '../../../../../functions/cachedImage';
import { getPayLinkDoc, addDataToCollection } from '../../../../../../db';
import { receiveSparkLightningPayment } from '../../../../../functions/spark';
import { isBlitzLNURLAddress } from '../../../../../functions/lnurl';
import { handleBrantaVerification } from '../../../../../functions/branta/index';
import { Image as ExpoImage } from 'expo-image';
export default async function decodeSendAddress(props) {
let {
btcAdress,
@@ -212,33 +215,6 @@ export default async function decodeSendAddress(props) {
}
}
// handle bip21 qrs
// if (
// btcAdress.toLowerCase().startsWith('lightning') ||
// btcAdress.toLowerCase().startsWith('bitcoin')
// ) {
// console.log(btcAdress);
// const decodedAddress = decodeBip21Address(
// btcAdress,
// btcAdress.toLowerCase().startsWith('lightning')
// ? 'lightning'
// : 'bitcoin',
// );
// console.log(decodedAddress);
// const lightningInvoice = btcAdress.toLowerCase().startsWith('lightning')
// ? decodedAddress.address.toUpperCase()
// : decodedAddress.options.lightning?.toUpperCase();
// console.log(lightningInvoice);
// if (lightningInvoice)
// btcAdress = await hanndleLNURLAddress(lightningInvoice);
// }
// if (btcAdress.toLowerCase().startsWith('lnurl')) {
// btcAdress = await hanndleLNURLAddress(btcAdress);
// }
console.log(btcAdress, 'bitcoin address');
let input;
@@ -256,37 +232,74 @@ export default async function decodeSendAddress(props) {
}
let processedPaymentInfo;
let brantaVerification;
try {
processedPaymentInfo = await processInputType(input, {
fiatStats,
liquidNodeInformation,
masterInfoObject,
navigate,
// maxZeroConf,
comingFromAccept,
enteredPaymentInfo,
setPaymentInfo,
// webViewRef,
setLoadingMessage,
paymentInfo,
fromPage,
seletctedToken,
currentWalletMnemoinc,
t,
sendWebViewRequest,
contactInfo,
sparkInformation,
globalContactsInformation,
accountMnemoinc,
usablePaymentMethod,
bitcoinBalance,
dollarBalanceSat,
convertedSendAmount,
poolInfoRef,
swapLimits,
// usd_multiplier_coefiicent,
min_usd_swap_amount,
});
let shouldRunBrantaVerification = false;
if (input.type === InputTypes.BOLT11) shouldRunBrantaVerification = true;
if (input.type === InputTypes.BITCOIN_ADDRESS) {
try {
const url = new URL(btcAdress);
shouldRunBrantaVerification =
url.searchParams.has('branta_id') &&
url.searchParams.has('branta_secret');
} catch {
shouldRunBrantaVerification = false;
}
}
const brantaVerificationPromise = shouldRunBrantaVerification
? Promise.race([
handleBrantaVerification(btcAdress),
new Promise(resolve => setTimeout(() => resolve(null), 2000)),
])
: Promise.resolve(null);
if (shouldRunBrantaVerification) {
brantaVerificationPromise.then(brantaResult => {
if (brantaResult && brantaResult.length) {
const [details] = brantaResult;
if (details.platformLogoUrl) {
ExpoImage.prefetch(details.platformLogoUrl).catch(err =>
console.log('Error prefetching branta merchant logo', err),
);
}
}
});
}
[processedPaymentInfo, brantaVerification] = await Promise.all([
processInputType(input, {
fiatStats,
liquidNodeInformation,
masterInfoObject,
navigate,
// maxZeroConf,
comingFromAccept,
enteredPaymentInfo,
setPaymentInfo,
// webViewRef,
setLoadingMessage,
paymentInfo,
fromPage,
seletctedToken,
currentWalletMnemoinc,
t,
sendWebViewRequest,
contactInfo,
sparkInformation,
globalContactsInformation,
accountMnemoinc,
usablePaymentMethod,
bitcoinBalance,
dollarBalanceSat,
convertedSendAmount,
poolInfoRef,
swapLimits,
// usd_multiplier_coefiicent,
min_usd_swap_amount,
}),
brantaVerificationPromise,
]);
} catch (err) {
return goBackFunction(
err.message ||
@@ -301,6 +314,23 @@ export default async function decodeSendAddress(props) {
};
}
if (brantaVerification && brantaVerification.length) {
const [details] = brantaVerification;
const isHttpsUrl = val =>
typeof val === 'string' && val.startsWith('https://');
processedPaymentInfo = {
...processedPaymentInfo,
isUsingBranta: true,
brantaMerchantName: details.platform,
brantaMerchantLogo: isHttpsUrl(details.platformLogoUrl)
? details.platformLogoUrl
: undefined,
verificationURL: isHttpsUrl(details.verifyUrl)
? details.verifyUrl
: undefined,
};
}
if (processedPaymentInfo) {
// const isLRC20 =
// seletctedToken?.tokenMetadata?.tokenTicker !== undefined &&
@@ -193,6 +193,7 @@ export default function SendPaymentScreen(props) {
const isBitcoinPayment = paymentInfo?.paymentNetwork === 'Bitcoin';
const isSparkPayment = paymentInfo?.paymentNetwork === 'spark';
const isLNURLPayment = paymentInfo?.type === InputTypes.LNURL_PAY;
const isUsingBranta = paymentInfo?.isUsingBranta;
const enabledLRC20 = showTokensInformation;
const defaultToken = enabledLRC20
@@ -1320,6 +1321,13 @@ export default function SendPaymentScreen(props) {
};
}, [isAmountFocused, bottomPadding]);
const handleBrandaVerificationUrl = useCallback(() => {
navigate.navigate('CustomWebView', {
headerText: 'Branta',
webViewURL: paymentInfo?.verificationURL,
});
}, [paymentInfo?.verificationURL, navigate, t]);
const sendingAsset =
selectedLRC20Asset === 'Bitcoin'
? !isLightningPayment &&
@@ -1348,7 +1356,13 @@ export default function SendPaymentScreen(props) {
return (
<CustomKeyboardAvoidingView globalThemeViewStyles={memorizedKeyboardStyle}>
<View style={styles.replacementContainer}>
<CustomSettingsTopBar label={t('constants.send')} />
<CustomSettingsTopBar
label={t('constants.send')}
showLeftImage={isUsingBranta}
iconNew="BadgeCheck"
leftImageStyles={{ height: 25 }}
leftImageFunction={handleBrandaVerificationUrl}
/>
<ScrollView contentContainerStyle={styles.balanceScrollContainer}>
{/* Amount display */}
{uiState !== 'SWAP_RATES_CHANGED' && (
@@ -1428,6 +1442,7 @@ export default function SendPaymentScreen(props) {
}
theme={theme}
darkModeType={darkModeType}
isUsingBranta={isUsingBranta}
/>
)}
{uiState === 'CHOOSE_METHOD' && (
+16
View File
@@ -0,0 +1,16 @@
import { BrantaServerBaseUrl, V2BrantaClient } from '@branta-ops/branta';
const brantaClient = new V2BrantaClient({
baseUrl: BrantaServerBaseUrl.Production,
privacy: 'strict',
});
export async function handleBrantaVerification(qrCode) {
try {
const brantaResponse = await brantaClient.getPaymentsByQRCode(qrCode ?? '');
return brantaResponse;
} catch (err) {
console.log('Error retriving branta verification', err);
return null;
}
}
+2 -1
View File
@@ -1170,7 +1170,8 @@
"swapRatesChangedTitle": "Wechselkurs aktualisiert",
"swapRatesChangedBody": "Der Wechselkurs für diesen Tausch hat sich während Ihrer Überprüfung geändert. Bitte geben Sie den Betrag erneut ein, um fortzufahren.",
"swapRatesChangedButton": "Betrag erneut eingeben",
"feeEstimateError": "Gebühr konnte nicht geschätzt werden, bitte versuchen Sie es erneut"
"feeEstimateError": "Gebühr konnte nicht geschätzt werden, bitte versuchen Sie es erneut",
"brantaVerification": "Diese Zahlung wurde von Branta verifiziert"
},
"selectPaymentMethod": {
"header": "Bitte wählen Sie aus, wie Sie Ihre Zahlung senden möchten (BTC oder USD)."
+2 -1
View File
@@ -1170,7 +1170,8 @@
"swapRatesChangedTitle": "Swap Rate Updated",
"swapRatesChangedBody": "The exchange rate for this swap changed while you were reviewing. Please re-enter your amount to continue.",
"swapRatesChangedButton": "Re-enter Amount",
"feeEstimateError": "Unable to estimate fee, please try again"
"feeEstimateError": "Unable to estimate fee, please try again",
"brantaVerification": "This payment has been verified by Branta"
},
"selectPaymentMethod": {
"header": "How do you want to fund your payment?"
+2 -1
View File
@@ -1170,7 +1170,8 @@
"swapRatesChangedTitle": "Tipo de cambio actualizado",
"swapRatesChangedBody": "El tipo de cambio para este intercambio cambió mientras lo estabas revisando. Vuelve a ingresar el monto para continuar.",
"swapRatesChangedButton": "Volver a ingresar el monto",
"feeEstimateError": "No se pudo estimar la comisión, inténtalo de nuevo"
"feeEstimateError": "No se pudo estimar la comisión, inténtalo de nuevo",
"brantaVerification": "Este pago ha sido verificado por Branta"
},
"selectPaymentMethod": {
"header": "Selecciona cómo deseas financiar tu pago."
+2 -1
View File
@@ -1170,7 +1170,8 @@
"swapRatesChangedTitle": "Taux de change mis à jour",
"swapRatesChangedBody": "Le taux de change pour cet échange a changé pendant votre vérification. Veuillez saisir à nouveau le montant pour continuer.",
"swapRatesChangedButton": "Ressaisir le montant",
"feeEstimateError": "Impossible destimer les frais, veuillez réessayer"
"feeEstimateError": "Impossible destimer les frais, veuillez réessayer",
"brantaVerification": "Ce paiement a été vérifié par Branta"
},
"selectPaymentMethod": {
"header": "Veuillez sélectionner comment vous souhaitez financer votre paiement."
+2 -1
View File
@@ -1170,7 +1170,8 @@
"swapRatesChangedTitle": "Tasso di cambio aggiornato",
"swapRatesChangedBody": "Il tasso di cambio per questo scambio è cambiato durante la revisione. Inserisci nuovamente limporto per continuare.",
"swapRatesChangedButton": "Reinserisci importo",
"feeEstimateError": "Impossibile stimare la commissione, riprova"
"feeEstimateError": "Impossibile stimare la commissione, riprova",
"brantaVerification": "Questo pagamento è stato verificato da Branta"
},
"selectPaymentMethod": {
"header": "Seleziona come desideri finanziare il pagamento."
+2 -1
View File
@@ -1170,7 +1170,8 @@
"swapRatesChangedTitle": "Cotação atualizada",
"swapRatesChangedBody": "A cotação para esta troca mudou enquanto você estava revisando. Insira o valor novamente para continuar.",
"swapRatesChangedButton": "Inserir valor novamente",
"feeEstimateError": "Não foi possível estimar a taxa, tente novamente"
"feeEstimateError": "Não foi possível estimar a taxa, tente novamente",
"brantaVerification": "Este pagamento foi verificado pela Branta"
},
"selectPaymentMethod": {
"header": "Escolha o saldo para realizar esse pagamento"
+2 -1
View File
@@ -1171,7 +1171,8 @@
"swapRatesChangedTitle": "Курс обмена обновлён",
"swapRatesChangedBody": "Курс обмена для этой операции изменился во время проверки. Пожалуйста, введите сумму заново, чтобы продолжить.",
"swapRatesChangedButton": "Ввести сумму снова",
"feeEstimateError": "Не удалось оценить комиссию, попробуйте снова"
"feeEstimateError": "Не удалось оценить комиссию, попробуйте снова",
"brantaVerification": "Этот платёж был подтверждён Branta"
},
"selectPaymentMethod": {
"header": "Выберите источник средств."
+2 -1
View File
@@ -1170,7 +1170,8 @@
"swapRatesChangedTitle": "Växelkurs uppdaterad",
"swapRatesChangedBody": "Växelkursen för denna växling ändrades medan du granskade den. Ange beloppet igen för att fortsätta.",
"swapRatesChangedButton": "Ange belopp igen",
"feeEstimateError": "Kunde inte uppskatta avgiften, försök igen"
"feeEstimateError": "Kunde inte uppskatta avgiften, försök igen",
"brantaVerification": "Denna betalning har verifierats av Branta"
},
"selectPaymentMethod": {
"header": "Välj hur du vill finansiera din betalning."
+1
View File
@@ -18,6 +18,7 @@
},
"dependencies": {
"@azure/core-asynciterator-polyfill": "^1.0.2",
"@branta-ops/branta": "2.0.0",
"@breeztech/react-native-breez-sdk-liquid": "^0.11.9",
"@buildonspark/spark-sdk": "^0.8.0",
"@craftzdog/react-native-buffer": "^6.1.0",
+24 -1
View File
@@ -5,10 +5,33 @@ import 'react-native-gesture-handler';
import '@azure/core-asynciterator-polyfill';
//neeed for encription + spark
import 'react-native-quick-base64';
import { Buffer } from '@craftzdog/react-native-buffer';
import { pbkdf2Sync, createHash } from 'react-native-quick-crypto';
import QuickCrypto, {
pbkdf2Sync,
createHash,
subtle,
} from 'react-native-quick-crypto';
global.Buffer = Buffer;
// Polyfill Web Crypto API for packages that rely on crypto.subtle (e.g. @branta-ops/branta)
// react-native-get-random-values (imported above) already provides crypto.getRandomValues
// need for @branta-ops/branta
global.crypto = QuickCrypto;
global.crypto.subtle = subtle;
global.crypto.randomUUID =
global.crypto.randomUUID ||
function () {
const bytes = new Uint8Array(16);
global.crypto.getRandomValues(bytes);
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;
const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0'));
return `${hex.slice(0, 4).join('')}-${hex.slice(4, 6).join('')}-${hex
.slice(6, 8)
.join('')}-${hex.slice(8, 10).join('')}-${hex.slice(10).join('')}`;
};
// import 'text-encoding-polyfill';
import 'text-encoding'; // needed for spark
+8
View File
@@ -1776,6 +1776,13 @@ __metadata:
languageName: node
linkType: hard
"@branta-ops/branta@npm:2.0.0":
version: 2.0.0
resolution: "@branta-ops/branta@npm:2.0.0"
checksum: 6966c87c85ee8547541849aa5b5499c8927b5808f79ab4f96f3ad38c0ff21f1162f381c1f4c196adf4ee6b9378e09463e0c2fe81a03d4c58f724ae4a0adce4e0
languageName: node
linkType: hard
"@breeztech/react-native-breez-sdk-liquid@npm:^0.11.9":
version: 0.11.9
resolution: "@breeztech/react-native-breez-sdk-liquid@npm:0.11.9"
@@ -4912,6 +4919,7 @@ __metadata:
"@babel/plugin-transform-class-static-block": ^7.28.3
"@babel/preset-env": ^7.28.3
"@babel/runtime": ^7.25.0
"@branta-ops/branta": 2.0.0
"@breeztech/react-native-breez-sdk-liquid": ^0.11.9
"@buildonspark/spark-sdk": ^0.8.0
"@craftzdog/react-native-buffer": ^6.1.0