LNURLP Response
After retrieving the sending VASP's public key in the previous section, we can use it to verify the signature on the
LnurlpRequest
.# Before verifying a signature, make sure the request is an UMA request
# (rather than a regular lnurl request).
if not lnurlp_request.is_uma_request():
return
# First create an instance of INonceCache to prevent replay attacks.
# Here, we'll use an InMemoryNonceCache.
nonce_cache = InMemoryNonceCache(datetime.now(timezone.utc))
try:
uma.verify_uma_lnurlp_query_signature(
lnurlp_request,
other_vasp_pubkey_response,
nonce_cache,
)
except Exception as e:
# Handle the error and return a failure status.
Once the sending VASP's signature is verified, the receiving VASP can create and send the
LnurlpResponse
.payer_data_options = create_counterparty_data_options(
{
"name": False,
"email": False,
"identifier": True,
"compliance": True,
}
)
response = uma.create_uma_lnurlp_response(
request=lnurlp_request,
signing_private_key=signing_private_key,
requires_travel_rule_info=True,
callback=callback,
encoded_metadata=encoded_metadata,
min_sendable_sats=1,
max_sendable_sats=10_000_000,
payer_data_options=payer_data_options,
currency_options=[
uma.Currency(
code="USD",
name="US Dollar",
symbol="$",
millisatoshi_per_unit=34_150,
max_sendable=1,
min_sendable=10_000_000,
decimals=2,
),
uma.Currency(
code="SAT",
name="Satoshi",
symbol="sat",
millisatoshi_per_unit=1000,
max_sendable=1,
min_sendable=10_000_000_000,
decimals=0,
),
uma.Currency(
code="MXN",
name="Mexican Pesos",
symbol="Mex$",
millisatoshi_per_unit=1820,
max_sendable=1,
min_sendable=10_000_000,
decimals=0,
),
],
receiver_kyc_status=KycStatus.VERIFIED,
)
The
encodedMetadata
param is the json-encoded metadata like with standard lnurl-pay. For example, it might look something like:[
["text/plain", "Pay someone on Vasp2"],
["text/identifier", "someone@vasp2.org"],
]
The
callback
param is the same as in lnurl-pay - it's the URL which the sending VASP should call to complete the pay request. If you'd like, you can include query params in this callback URL. For example, maybe you want to include some tracking request identifier between calls to save and retrieve the encoded metadata when completing the pay request later.The
payerDataOptions
param indicates what information, such as payment or compliance information, the receiving VASP requires from the sending VASP in the next request (payreq). This follows the LUD-18 spec, but with an added “compliance” option, which should be set to “true” for UMA requests involving one or more VASPs.Each entry in the
currencies
parameter array is an extension of the proposed LUD-21 spec:@dataclass
class Currency:
code: str
"""
ISO 4217 currency code (if applicable). For example, USD for US Dollars. For
cryptocurrencies, this will be a ticker symbol, such as BTC for Bitcoin.
"""
name: str
"""
Full display name of the currency. For example, in USD, the name is "US Dollars".
"""
symbol: str
"""
Symbol for this currency. For example, in USD, the symbol is "$".
"""
millisatoshi_per_unit: float
"""
Estimated millisats per smallest "unit" of this currency (eg. 1 cent in USD).
"""
min_sendable: int
"""
Minimum amount that can be sent in this currency. This is in the smallest unit
of the currency (eg. cents for USD).
"""
max_sendable: int
"""
Maximum amount that can be sent in this currency. This is in the smallest unit
of the currency (eg. cents for USD).
"""
decimals: int
"""
The number of digits after the decimal point for display on the sender side, and
to add clarity around what the "smallest unit" of the currency is. For example,
in USD, by convention, there are 2 digits for cents - $5.95. In this case,
`decimals` would be 2. Note that the multiplier is still always in the smallest
unit (cents). In addition to display purposes, this field can be used to resolve
ambiguity in what the multiplier means. For example, if the currency is "BTC"
and the multiplier is 1000, really we're exchanging in SATs, so `decimals` would
be 8. For details on edge cases and examples, see
https://github.com/uma-universal-money-address/protocol/blob/main/umad-04-lnurlp-response.md.
"""
uma_major_version: int = MAJOR_VERSION
"""
The major version of the UMA protocol that this currency adheres to. This is not
serialized to JSON.
"""
The
currencies
param might be user-dependent based on the recipient's preferred currency and the receiving VASP's capabilities. At this stage, this can be viewed as an indication of the conversion rate the VASP would provide for the transaction - not a binding quote for the conversion. The conversion rate will come from your own calculations, and can be updated later to provide a binding quotation in the payreq step.Now that you have a response object, you can respond to the
LnurlpRequest
with the json-serialized response object.When the sending VASP receives the response, it can parse the response body as JSON using the SDK's helper function:
try:
lnurlp_response = uma.parse_lnurlp_response(response.text)
except Exception as e:
_abort_with_error(424, f"Error parsing LNURLP response: {e}")
The response received is decoded to an object that looks like:
@dataclass
class LnurlpResponse:
tag: str
callback: str
"""
The URL that the sender will call for the payreq request.
"""
min_sendable: int
"""
The minimum amount that the sender can send in millisatoshis.
"""
max_sendable: int
"""
The maximum amount that the sender can send in millisatoshis.
"""
encoded_metadata: str
"""
JSON-encoded metadata that the sender can use to display information to the user.
"""
currencies: Optional[List[Currency]]
"""
The list of currencies that the receiver accepts in order of preference.
"""
required_payer_data: Optional[CounterpartyDataOptions]
"""
The data about the payer that the sending VASP must provide in order to send a
payment.
"""
compliance: Optional[LnurlComplianceResponse]
"""
Compliance-related data from the receiving VASP.
"""
uma_version: Optional[str]
"""
The version of the UMA protocol that the receiver is using.
"""
comment_chars_allowed: Optional[int] = None
"""
The number of characters that the sender can include in the comment field of
the pay request. See LUD-12 for more details.
"""
nostr_pubkey: Optional[str] = None
"""
An optional nostr pubkey used for nostr zaps (NIP-57). If set, it should be a
valid BIP-340 public key in hex format.
"""
allows_nostr: Optional[bool] = None
"""
Should be set to true if the receiving VASP allows nostr zaps (NIP-57).
"""
The
payerData
field of the LnurlpResponse
indicates what metadata VASP2 requires from VASP1 about the sender in the next request. Its structure is JSON encoded as specified by LUD-18 as:{
// Note that the sender identifier is always required in UMA.
"identifier": { "mandatory": true },
"name": { "mandatory": boolean },
"email": { "mandatory": boolean },
// New! Indicates we want uma compliance data.
"compliance":{ "mandatory": boolean },
}
The
compliance
field of the LnurlpResponse
contains UMA-specific metadata from VASP2 related to compliance. Its structure is:@dataclass
class LnurlComplianceResponse:
kyc_status: KycStatus
"""
Whether the receiver is KYC verified by the receiving VASP.
"""
signature: str
"""
DER-encoded signature from the receiving VASP.
"""
signature_nonce: str
"""
A random string included in the signature payload to prevent replay attacks.
"""
signature_timestamp: int
"""
The time at which the request was made.
"""
is_subject_to_travel_rule: bool
"""
Whether the receiving VASP is subject to travel rule regulations.
"""
receiver_identifier: str
"""
The UMA address of the receiver.
"""
At this point, the sending VASP knows the receiver is a verified user at the receiving VASP. It knows the receiver's preferred currencies and an indication or estimate of the corresponding exchange rates. The sending VASP's wallet application might then move to the amount entry screen, showing the preferred receiving currency and exchange rate from the sender's preferred currency.
When the user enters an amount and confirms, the sending VASP needs to send the payreq request to get an invoice that the sender can pay.