Changeset View
Changeset View
Standalone View
Standalone View
apps/marlin-wallet/web/src/bip21.ts
| // Copyright (c) 2025 The Bitcoin developers | // Copyright (c) 2025 The Bitcoin developers | ||||
| // Distributed under the MIT software license, see the accompanying | // Distributed under the MIT software license, see the accompanying | ||||
| // file COPYING or http://www.opensource.org/licenses/mit-license.php. | // file COPYING or http://www.opensource.org/licenses/mit-license.php. | ||||
| import { config } from './config'; | import { config } from './config'; | ||||
| import { isValidECashAddress } from './address'; | import { isValidECashAddress } from './address'; | ||||
| import { satsToXec } from './amount'; | import { atomsToUnit, unitToAtoms } from './amount'; | ||||
| import { XEC_ASSET } from './supported-assets'; | |||||
| /** | /** | ||||
| * Result of parsing a BIP21 URI | * Result of parsing a BIP21 URI | ||||
| */ | */ | ||||
| export interface Bip21ParseResult { | export interface Bip21ParseResult { | ||||
| address: string; | address: string; | ||||
| sats?: number; | sats?: number; | ||||
| // OP_RETURN data in hex (without 6a OP_RETURN opcode) | // OP_RETURN data in hex (without 6a OP_RETURN opcode) | ||||
| Show All 12 Lines | export function createBip21Uri(address: string, amountSats?: number): string { | ||||
| const rawAddress = address.includes(':') ? address.split(':')[1] : address; | const rawAddress = address.includes(':') ? address.split(':')[1] : address; | ||||
| // Build BIP21 URI with config prefix | // Build BIP21 URI with config prefix | ||||
| let bip21Uri = config.bip21Prefix + rawAddress; | let bip21Uri = config.bip21Prefix + rawAddress; | ||||
| // Add amount parameter if provided and positive | // Add amount parameter if provided and positive | ||||
| if (amountSats && amountSats > 0) { | if (amountSats && amountSats > 0) { | ||||
| // Convert satoshis to XEC using the standard conversion function | // Convert satoshis to XEC using the standard conversion function | ||||
| const amountXec = satsToXec(amountSats); | const amountXec = atomsToUnit(amountSats, XEC_ASSET.decimals); | ||||
| // Format with 2 decimal places | bip21Uri += `?amount=${amountXec.toFixed(XEC_ASSET.decimals)}`; | ||||
| bip21Uri += `?amount=${amountXec.toFixed(2)}`; | |||||
| } | } | ||||
| return bip21Uri; | return bip21Uri; | ||||
| } | } | ||||
| /** | /** | ||||
| * Parse a BIP21 URI string | * Parse a BIP21 URI string | ||||
| * | * | ||||
| Show All 35 Lines | try { | ||||
| // Amount in BIP21 is specified in XEC, we convert to satoshis (1 XEC = 100 sats) | // Amount in BIP21 is specified in XEC, we convert to satoshis (1 XEC = 100 sats) | ||||
| const amountParam = url.searchParams.get('amount'); | const amountParam = url.searchParams.get('amount'); | ||||
| if (amountParam) { | if (amountParam) { | ||||
| // Parse as floating point number (XEC) | // Parse as floating point number (XEC) | ||||
| const amountXec = parseFloat(amountParam); | const amountXec = parseFloat(amountParam); | ||||
| // Validate that it's a valid number and positive | // Validate that it's a valid number and positive | ||||
| if (!isNaN(amountXec) && amountXec > 0) { | if (!isNaN(amountXec) && amountXec > 0) { | ||||
| // Convert XEC to satoshis (1 XEC = 100 sats) and ensure it's an integer | result.sats = unitToAtoms(amountXec, XEC_ASSET.decimals); | ||||
| result.sats = Math.round(amountXec * 100); | |||||
| } | } | ||||
| } | } | ||||
| // Parse op_return_raw parameter if present | // Parse op_return_raw parameter if present | ||||
| const opReturnRaw = url.searchParams.get('op_return_raw'); | const opReturnRaw = url.searchParams.get('op_return_raw'); | ||||
| if (opReturnRaw) { | if (opReturnRaw) { | ||||
| // Validate it's a valid hex string with even number of characters | // Validate it's a valid hex string with even number of characters | ||||
| const cleanHex = opReturnRaw.trim().toUpperCase(); | const cleanHex = opReturnRaw.trim().toUpperCase(); | ||||
| Show All 10 Lines | |||||