The VASP UMA Auth OpenAPI Schema
The VASP UMA Auth API schema is a RESTful API that the NWC image uses to communicate to the main VASP server. It uses the same sort of functionality
that the VASP has already implemented for the UMA standard, so it's fairly easy to implement once you have UMA built.
You can find the VASP UMA Auth OpenAPI schema in the uma-auth-api GitHub repository.
This same github repository also contains generated model libraries for Python, Go, Kotlin/Java, and TypeScript. Alternatively, can also generate
your own libraries for whatever framework or language you're using from the schema itself. You can see the full list of
OpenAPI generators here.
See the full API spec documentation here.
You'll need to implement the specified API endpoints in your VASP server to handle the NWC image requests.
Technically, you do not need to support all of the endpoints in the schema, but the more you support, the more
functionality you can provide to your users. At a minimum, your existing UMA VASP implementation will give you a
simple starting point for implementing:
GET /receiver/lud16/{receiver_address}
: This is equivalent to the lnurlp request in UMA.GET /quote/lud16
: This is equivalent to the payreq request in UMA.POST /quote/{payment_hash}
: This actually pays a quote that was previously generated.POST /payments/lud16
: This is all 3 of the above combined into one request. Note that with this function, you can only lock the sending side, not receiving side.
You also must support the following endpoints for the NWC image to work:
GET /info
: Get information about the user's wallet connection, default currencies, etc.GET /budget_estimate
: Used to get a cost estimate in the user's default currency if they are making a payment in a different currency.
If you have BTC support and can send/recieve via Lightning for a user, you should also implement these to provide
functionality for SATs-only NWC applications:
POST /invoice
: Create a BOLT-11 invoice for a payment.POST /payments/bolt11
: Pay a BOLT-11 invoice.POST /payments/keysend
: Pay via keysend. This is slightly less critical to support than the other two.
These functions let applications query the user's balance and transactions. These are sensitive operations that
require explicit permission from the user. They are not strictly necessary for you to support, but they are useful
for applications that want to show the user's full balance or transaction history:
GET /balance
: Get the user's balance in the specified currency (or default currency).GET /invoices/{payment_hash}
: Fetch an invoice by payment hash. Includes the payment status.GET /transactions
: Fetch the user's transaction history.
In general, supporting these endpoints usually just entails mapping the API request data type to your existing
UMA implementation, and then mapping it back to the API response data type. The NWC image will handle the
communication with the client application via NWC.
Here's a simple example of what the
GET /receiver/lud16/{receiver_address}
endpoint might look like in Python:from flask import Flask, request, jsonify
# If importing the generated models libary (pip install uma_auth), you would import them like this:
from uma_auth.models.lookup_user_response import LookupUserResponse
app = Flask(__name__)
@app.route('/receiver/lud16/<receiver_address>', methods=['GET'])
def lookup_user(receiver_address):
# Assuming you have a `sending_vasp` object that can handles the UMA lnurlp lookup:
lookup_response = sending_vasp.handle_uma_lookup(receiver_uma)
currencies = lookup_response.get("receiverCurrencies") or []
return LookupUserResponse(
currencies=[
CurrencyPreference(
currency=Currency(
code=currency.get("code"),
symbol=currency.get("symbol"),
decimals=currency.get("decimals"),
name=currency.get("name"),
),
multiplier=currency.get("multiplier"),
min=(
currency.get("convertible").get("min")
if "convertible" in currency
else currency.get("minSendable")
),
max=(
currency.get("convertible").get("max")
if "convertible" in currency
else currency.get("maxSendable")
),
)
for currency in currencies
]
).to_dict()
The NWC docker image handles setting and tracking connection budgets for you, and will ensure that a given
app connection does not exceed the budget set by the user. However, there is one aspect that needs to be handled
by your main VASP server: currency conversion rates. When a user creates a new connection, their budget will be set
in their default currency. If they are making a payment in a different currency, the NWC image will query your
main VASP server for the current conversion rate so that the NWC docker image can deduct the right amount from
the budget. For example, say a user's default currency is USD and their budget for a connection is $10 USD per month.
If an app calls
pay_invoice
with a payment request for 0.001 BTC, the NWC image needs your main VASP server
to tell it how much to deduct from the user's budget, since it's the one setting the exchange rate.There are two places where you'll need to consider currency conversion for budgets - putting a hold on a budget
amount before executing a payment, and then when the user actually makes the payment successfully.
Before a payment, if the budget currency is different from the payment currency, the NWC image will call the
GET /budget_estimate
endpoint to get a cost estimate in the user's default currency. This endpoint simply
takes a sending currency code, an amount, and a budget currency code. You'll just need to return the estimated
cost in the budget currency code. The NWC image will then deduct this amount (plus a configurable buffer)
from the user's budget. Note that this is just an estimate, so it doesn't need to be exact. The exact
amount will be deducted when the payment succeeds. If the payment fails, the budget will be refunded.The second place you'll need to consider currency conversion is when the user actually completes a payment.
At that point, if the budget currency is different from the payment currency, you will need to populate the
total_budget_currency_amount
field in the response to the NWC image. This field is present in the following calls:POST /payments/lud16
(pay_to_address)POST /quote/{payment_hash}
(execute_quote)POST /payments/bolt11
(pay_invoice)POST /payments/keysend
(pay_keysend)
For each of these calls, make sure that if the budget currency is different from the payment currency you
populate the
total_budget_currency_amount
field with the amount that should be deducted from the user's budget.
For each of these requests, the budget currency code for the given connection will be provided in the
budget_currency_code
field of the request.All requests to the UMA Auth API should be authenticated using a JWT as described by the
VASP Token Exchange guide. The NWC image will use the long-lived token
associated with an NWC connection to authenticate to the API. The only exception is that it will occasionally
use the short-lived token to authenticate when querying
GET /info
to get the currency list when showing
the permission approval screen. You can add scopes to the JWTs to differentiate between these two tokens. You
can even add scopes for each command permission that the user has approved if you want to get really granular
with your permissions when checking the JWT in each API request. The JWT's sub
claim will be your user's
ID in your system, which you can use to authenticate them.import jwt
def user_id_from_jwt(request):
auth_header = request.headers.get("Authorization")
if not auth_header:
abort_with_error(401, "Unauthorized")
jwt_token = auth_header.split("Bearer ")[-1]
jwt_public_key = current_app.config.get("NWC_JWT_PUBKEY")
if not jwt_public_key:
abort_with_error(500, "JWT public key not configured")
try:
decoded = jwt.decode(
jwt_token,
key=jwt_public_key,
algorithms=["ES256"],
issuer="coolvasp.net",
audience="coolvasp.net",
)
# Optionally check scopes here.
return decoded.get("sub")
except jwt.exceptions.InvalidTokenError as e:
print("Invalid token error", e)
abort_with_error(401, "Unauthorized")
Optionally, if you're hosting the NWC image in the same VPC as your VASP server, you can limit access to the API
to only requests internal to your network. Only the NWC image should be making requests to this API, so there's
no need to expose it to the public internet, unless you want to for debugging, or you want to allow general HTTP
access to the API for other reasons.
Permissions in NWC and UMA Auth are tied explicitly to the commands that an application is allowed to execute on a
wallet (e.g.
pay_invoice
, get_balance
, etc.). When a user approves a connection, they are approving a set of
commands that the application can execute. The NWC image will enforce these permissions when an application makes
a request to the VASP server. It also enforces the budget set by the user for the connection, so you don't need to
worry about that in your main VASP server.However, for your convenience, the NWC image will include the approved commands when exchanging a short-lived token
for a long-lived token. This way, you can add those commands as scopes in the JWT if you'd like, and then check
those scopes in your API requests to ensure that the user has approved the command that the application is trying
to execute. Again, this is optional, but will give you some piece of mind that tokens you create can only be used
for the commands that the user has approved.
Here is the full mapping of NWC commands to UMA Auth API methods:
pay_invoice
->POST /payments/bolt11
pay_keysend
->POST /payments/keysend
make_invoice
->POST /invoice
get_balance
->GET /balance
get_invoices
->GET /invoices/{payment_hash}
get_transactions
->GET /transactions
lookup_user
->GET /receiver/lud16/{receiver_address}
fetch_quote
->GET /quote/lud16
execute_quote
->POST /quote/{payment_hash}
pay_to_address
->POST /payments/lud16
get_info
and get_budget_estimate
should generally always be allowed for any connection.- UMA Auth API Reference - Web Markdown
- UMA Auth OpenAPI Schema
- Generated Python Model Library also in pypi as
uma-auth
- Generated Go Model Library
- Generated Kotlin/Java Model Library
- Generated TypeScript Model Library
- Alternative OpenAPI Generators